diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index d593453abf8..baa02f256e3 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -48,6 +48,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/planner.h" #include "parser/parse_coerce.h" +#include "parser/parsetree.h" #include "pgstat.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -710,6 +711,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, { Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot; + TupleDesc output_tupdesc; + MemoryContext oldcontext; bool needslow = false; if (isDone) @@ -785,8 +788,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, /* 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, @@ -858,10 +859,61 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, needslow = true; /* need runtime check for null */ } + /* + * Use the variable's declared rowtype as the descriptor for the + * output values. In particular, we *must* absorb any attisdropped + * markings. + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + output_tupdesc = CreateTupleDescCopy(var_tupdesc); + MemoryContextSwitchTo(oldcontext); + ReleaseTupleDesc(var_tupdesc); } + else + { + /* + * In the RECORD case, we use the input slot's rowtype as the + * descriptor for the output values, modulo possibly assigning new + * column names below. + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); + MemoryContextSwitchTo(oldcontext); + } - /* Skip the checking on future executions of node */ + /* + * Construct a tuple descriptor for the composite values we'll produce, + * and make sure its record type is "blessed". The main reason to do this + * is to be sure that operations such as row_to_json() will see the + * desired column names when they look up the descriptor from the type + * information embedded in the composite values. + * + * We already got the correct physical datatype info above, but now we + * should try to find the source RTE and adopt its column aliases, in case + * they are different from the original rowtype's names. But to minimize + * compatibility breakage, don't do this for Vars of named composite + * types, only for Vars of type RECORD. + * + * If we can't locate the RTE, assume the column names we've got are OK. + * (As of this writing, the only cases where we can't locate the RTE are + * in execution of trigger WHEN clauses, and then the Var will have the + * trigger's relation's rowtype, so its names are fine.) + */ + if (variable->vartype == RECORDOID && + econtext->ecxt_estate && + variable->varno <= list_length(econtext->ecxt_estate->es_range_table)) + { + RangeTblEntry *rte = rt_fetch(variable->varno, + econtext->ecxt_estate->es_range_table); + + ExecTypeSetColNames(output_tupdesc, rte->eref->colnames); + } + + /* Bless the tupdesc if needed, and save it in the execution state */ + wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc); + + /* Skip all the above on future executions of node */ if (needslow) wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow; else @@ -884,7 +936,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, { Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot; - TupleDesc slot_tupdesc; HeapTupleHeader dtuple; if (isDone) @@ -914,34 +965,16 @@ 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. */ dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot)); /* - * If the Var identifies a named composite type, label the datum with that - * type; otherwise we'll use the slot's info. + * Label the datum with the composite type info we identified before. */ - if (variable->vartype != RECORDOID) - { - HeapTupleHeaderSetTypeId(dtuple, variable->vartype); - HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); - } + HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod); return PointerGetDatum(dtuple); } @@ -995,8 +1028,9 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, tuple = ExecFetchSlotTuple(slot); tupleDesc = slot->tts_tupleDescriptor; + /* wrv_tupdesc is a good enough representation of the Var's rowtype */ Assert(variable->vartype != RECORDOID); - var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + var_tupdesc = wrvstate->wrv_tupdesc; /* Check to see if any dropped attributes are non-null */ for (i = 0; i < var_tupdesc->natts; i++) @@ -1023,12 +1057,10 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot)); /* - * Reset datum's type ID fields to match the Var. + * Label the datum with the composite type info we identified before. */ - HeapTupleHeaderSetTypeId(dtuple, variable->vartype); - HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); - - ReleaseTupleDesc(var_tupdesc); + HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod); return PointerGetDatum(dtuple); } @@ -4356,6 +4388,7 @@ ExecInitExpr(Expr *node, PlanState *parent) WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState); wstate->parent = parent; + wstate->wrv_tupdesc = NULL; wstate->wrv_junkFilter = NULL; state = (ExprState *) wstate; state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar; diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 885959839cf..227e01c6191 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -983,6 +983,49 @@ ExecTypeFromExprList(List *exprList, List *namesList) return typeInfo; } +/* + * ExecTypeSetColNames - set column names in a TupleDesc + * + * Column names must be provided as an alias list (list of String nodes). + * We set names only in TupleDesc columns that lacked one before. + */ +void +ExecTypeSetColNames(TupleDesc typeInfo, List *namesList) +{ + bool modified = false; + int colno = 0; + ListCell *lc; + + foreach(lc, namesList) + { + char *cname = strVal(lfirst(lc)); + Form_pg_attribute attr; + + /* Guard against too-long names list */ + if (colno >= typeInfo->natts) + break; + attr = typeInfo->attrs[colno++]; + + /* Ignore empty aliases (these must be for dropped columns) */ + if (cname[0] == '\0') + continue; + + /* Change tupdesc only if we didn't have a name before */ + if (NameStr(attr->attname)[0] == '\0') + { + namestrcpy(&(attr->attname), cname); + modified = true; + } + } + + /* If we modified the tupdesc, it's now a new record type */ + if (modified) + { + typeInfo->tdtypeid = RECORDOID; + typeInfo->tdtypmod = -1; + } +} + /* * BlessTupleDesc - make a completed tuple descriptor useful for SRFs * diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index a3a73f344a3..f60d809bc98 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -263,6 +263,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate, extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid); extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid); extern TupleDesc ExecTypeFromExprList(List *exprList, List *namesList); +extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList); extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg); typedef struct TupOutputState diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 089f5fadc7f..b6657780106 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -569,6 +569,7 @@ typedef struct WholeRowVarExprState ExprState xprstate; struct PlanState *parent; /* parent PlanState, or NULL if none */ JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */ + TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */ } WholeRowVarExprState; /* ---------------- diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 5616d6f7f2a..7a3eb0e87c0 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -476,3 +476,164 @@ select (row('Jim', 'Beam')).text; -- error ERROR: could not identify column "text" in record data type LINE 1: select (row('Jim', 'Beam')).text; ^ +-- +-- Test that composite values are seen to have the correct column names +-- (bug #11210 and other reports) +-- +select row_to_json(i) from int8_tbl i; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(i) from int8_tbl i(x,y); + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +create temp view vv1 as select * from int8_tbl; +select row_to_json(i) from vv1 i; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(i) from vv1 i(x,y); + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1, q2 from int8_tbl) as ss; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1, q2 from int8_tbl offset 0) as ss; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss; + row_to_json +---------------------------------------------- + {"a":123,"b":456} + {"a":123,"b":4567890123456789} + {"a":4567890123456789,"b":123} + {"a":4567890123456789,"b":4567890123456789} + {"a":4567890123456789,"b":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss(x,y); + row_to_json +---------------------------------------------- + {"x":123,"y":456} + {"x":123,"y":4567890123456789} + {"x":4567890123456789,"y":123} + {"x":4567890123456789,"y":4567890123456789} + {"x":4567890123456789,"y":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y); + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +explain (costs off) +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + QUERY PLAN +------------------------------------------------------------------- + Subquery Scan on q + -> Limit + -> Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (tenthous < 2000)) +(4 rows) + +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + row_to_json +--------------------------------- + {"thousand":42,"tenthous":42} + {"thousand":42,"tenthous":1042} +(2 rows) + +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + row_to_json +------------------- + {"x":42,"y":42} + {"x":42,"y":1042} +(2 rows) + +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q(a,b); + row_to_json +------------------- + {"a":42,"b":42} + {"a":42,"b":1042} +(2 rows) + +create temp table tt1 as select * from int8_tbl limit 2; +create temp table tt2 () inherits(tt1); +insert into tt2 values(0,0); +select row_to_json(r) from (select q2,q1 from tt1 offset 0) r; + row_to_json +---------------------------------- + {"q2":456,"q1":123} + {"q2":4567890123456789,"q1":123} + {"q2":0,"q1":0} +(3 rows) + diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 65ebdc566ab..bc3f0210200 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -227,3 +227,47 @@ select cast (row('Jim', 'Beam') as text); select (row('Jim', 'Beam'))::text; select text(row('Jim', 'Beam')); -- error select (row('Jim', 'Beam')).text; -- error + +-- +-- Test that composite values are seen to have the correct column names +-- (bug #11210 and other reports) +-- + +select row_to_json(i) from int8_tbl i; +select row_to_json(i) from int8_tbl i(x,y); + +create temp view vv1 as select * from int8_tbl; +select row_to_json(i) from vv1 i; +select row_to_json(i) from vv1 i(x,y); + +select row_to_json(ss) from + (select q1, q2 from int8_tbl) as ss; +select row_to_json(ss) from + (select q1, q2 from int8_tbl offset 0) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss(x,y); +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y); + +explain (costs off) +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q(a,b); + +create temp table tt1 as select * from int8_tbl limit 2; +create temp table tt2 () inherits(tt1); +insert into tt2 values(0,0); +select row_to_json(r) from (select q2,q1 from tt1 offset 0) r;