#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"
{
Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
+ TupleDesc output_tupdesc;
+ MemoryContext oldcontext;
bool needslow = false;
if (isDone)
/* 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,
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
{
Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
- TupleDesc slot_tupdesc;
HeapTupleHeader dtuple;
if (isDone)
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);
}
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++)
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);
}
WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
wstate->parent = parent;
+ wstate->wrv_tupdesc = NULL;
wstate->wrv_junkFilter = NULL;
state = (ExprState *) wstate;
state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
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
*
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
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;
/* ----------------
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
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand = 42) AND (tenthous < 2000))
+(3 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)
+
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;