diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out index 388893a4793aa293a14930d31a833f67b887a3a7..813b9c04784ad97b9b0bc2d4ad111bb8686e3988 100644 --- a/contrib/hstore/expected/hstore.out +++ b/contrib/hstore/expected/hstore.out @@ -907,9 +907,9 @@ select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) -- records select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d); - hstore ------------------------------------------------- - "f1"=>"1", "f2"=>"foo", "f3"=>"1.2", "f4"=>"3" + hstore +-------------------------------------------- + "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3" (1 row) create domain hstestdom1 as integer not null default 0; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 4a6baeb17eea0146d4ca692b8992dd572c5db7a9..a1193a8dc34d8f5eab2d643898b7a905066d84de 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -4627,7 +4627,8 @@ ExecInitExpr(Expr *node, PlanState *parent) if (rowexpr->row_typeid == RECORDOID) { /* generic record, use runtime type assignment */ - rstate->tupdesc = ExecTypeFromExprList(rowexpr->args); + rstate->tupdesc = ExecTypeFromExprList(rowexpr->args, + rowexpr->colnames); BlessTupleDesc(rstate->tupdesc); /* we won't need to redo this at runtime */ } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 3a9471e462f1c455020ff4fee608482342e4619d..e755e7c4f07205db72687656d96ad7615adc04ce 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -954,27 +954,28 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) /* * ExecTypeFromExprList - build a tuple descriptor from a list of Exprs * - * Here we must make up an arbitrary set of field names. + * Caller must also supply a list of field names (String nodes). */ TupleDesc -ExecTypeFromExprList(List *exprList) +ExecTypeFromExprList(List *exprList, List *namesList) { TupleDesc typeInfo; - ListCell *l; + ListCell *le; + ListCell *ln; int cur_resno = 1; - char fldname[NAMEDATALEN]; + + Assert(list_length(exprList) == list_length(namesList)); typeInfo = CreateTemplateTupleDesc(list_length(exprList), false); - foreach(l, exprList) + forboth(le, exprList, ln, namesList) { - Node *e = lfirst(l); - - sprintf(fldname, "f%d", cur_resno); + Node *e = lfirst(le); + char *n = strVal(lfirst(ln)); TupleDescInitEntry(typeInfo, cur_resno, - fldname, + n, exprType(e), exprTypmod(e), 0); diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c index fc17677d0aceec3f6ba8f8ac64fdc309a8b34df3..a6c1b70cca65dcd28e88d9c225e247c3da7757aa 100644 --- a/src/backend/executor/nodeValuesscan.c +++ b/src/backend/executor/nodeValuesscan.c @@ -25,6 +25,7 @@ #include "executor/executor.h" #include "executor/nodeValuesscan.h" +#include "parser/parsetree.h" static TupleTableSlot *ValuesNext(ValuesScanState *node); @@ -188,6 +189,8 @@ ValuesScanState * ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags) { ValuesScanState *scanstate; + RangeTblEntry *rte = rt_fetch(node->scan.scanrelid, + estate->es_range_table); TupleDesc tupdesc; ListCell *vtl; int i; @@ -239,7 +242,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags) /* * get info about values list */ - tupdesc = ExecTypeFromExprList((List *) linitial(node->values_lists)); + tupdesc = ExecTypeFromExprList((List *) linitial(node->values_lists), + rte->eref->colnames); ExecAssignScanType(&scanstate->ss, tupdesc); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e99e4cc176182b24f1db19e2ee0d236cb64b905c..8f034176e7cc4535ed5e854d0a8a0dd76b7fdebc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -492,7 +492,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * reconstitute the RestrictInfo layer. */ childquals = get_all_actual_clauses(rel->baserestrictinfo); - childquals = (List *) adjust_appendrel_attrs((Node *) childquals, + childquals = (List *) adjust_appendrel_attrs(root, + (Node *) childquals, appinfo); childqual = eval_const_expressions(root, (Node *) make_ands_explicit(childquals)); @@ -532,10 +533,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * while constructing attr_widths estimates below, though. */ childrel->joininfo = (List *) - adjust_appendrel_attrs((Node *) rel->joininfo, + adjust_appendrel_attrs(root, + (Node *) rel->joininfo, appinfo); childrel->reltargetlist = (List *) - adjust_appendrel_attrs((Node *) rel->reltargetlist, + adjust_appendrel_attrs(root, + (Node *) rel->reltargetlist, appinfo); /* diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 2f22efabb53d341618289460e015ae09930352b2..9228f82920165b83c4f256cbe0d8e6cef6a15bbb 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -1810,7 +1810,8 @@ add_child_rel_equivalences(PlannerInfo *root, Expr *child_expr; child_expr = (Expr *) - adjust_appendrel_attrs((Node *) cur_em->em_expr, + adjust_appendrel_attrs(root, + (Node *) cur_em->em_expr, appinfo); (void) add_eq_member(cur_ec, child_expr, child_rel->relids, true, cur_em->em_datatype); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 2e8ea5afad78ec28ae85908cff430edab93bd9b8..8bbe97713bb13cc4d3c6fba638c2d771ee5901b3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -772,7 +772,8 @@ inheritance_planner(PlannerInfo *root) * then fool around with subquery RTEs. */ subroot.parse = (Query *) - adjust_appendrel_attrs((Node *) parse, + adjust_appendrel_attrs(root, + (Node *) parse, appinfo); /* diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index cff5a8653163fc64d45a642e8d17f94743be3f51..e361fb8ce96ef3ec1e0448c857cc2429c4687020 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -49,6 +49,12 @@ #include "utils/selfuncs.h" +typedef struct +{ + PlannerInfo *root; + AppendRelInfo *appinfo; +} adjust_appendrel_attrs_context; + static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root, double tuple_fraction, List *colTypes, List *colCollations, @@ -99,7 +105,7 @@ static void make_inh_translation_list(Relation oldrelation, static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, - AppendRelInfo *context); + adjust_appendrel_attrs_context *context); static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); @@ -1569,9 +1575,13 @@ translate_col_privs(const Bitmapset *parent_privs, * maybe we should try to fold the two routines together. */ Node * -adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo) +adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo) { Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.appinfo = appinfo; /* * Must be prepared to start with a Query or a bare expression tree. @@ -1582,7 +1592,7 @@ adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo) newnode = query_tree_mutator((Query *) node, adjust_appendrel_attrs_mutator, - (void *) appinfo, + (void *) &context, QTW_IGNORE_RC_SUBQUERIES); if (newnode->resultRelation == appinfo->parent_relid) { @@ -1596,14 +1606,17 @@ adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo) result = (Node *) newnode; } else - result = adjust_appendrel_attrs_mutator(node, appinfo); + result = adjust_appendrel_attrs_mutator(node, &context); return result; } static Node * -adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) { + AppendRelInfo *appinfo = context->appinfo; + if (node == NULL) return NULL; if (IsA(node, Var)) @@ -1611,22 +1624,22 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) Var *var = (Var *) copyObject(node); if (var->varlevelsup == 0 && - var->varno == context->parent_relid) + var->varno == appinfo->parent_relid) { - var->varno = context->child_relid; - var->varnoold = context->child_relid; + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; if (var->varattno > 0) { Node *newnode; - if (var->varattno > list_length(context->translated_vars)) + if (var->varattno > list_length(appinfo->translated_vars)) elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(context->parent_reloid)); - newnode = copyObject(list_nth(context->translated_vars, + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, var->varattno - 1)); if (newnode == NULL) elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(context->parent_reloid)); + var->varattno, get_rel_name(appinfo->parent_reloid)); return newnode; } else if (var->varattno == 0) @@ -1637,19 +1650,19 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) * step to convert the tuple layout to the parent's rowtype. * Otherwise we have to generate a RowExpr. */ - if (OidIsValid(context->child_reltype)) + if (OidIsValid(appinfo->child_reltype)) { - Assert(var->vartype == context->parent_reltype); - if (context->parent_reltype != context->child_reltype) + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) { ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); r->arg = (Expr *) var; - r->resulttype = context->parent_reltype; + r->resulttype = appinfo->parent_reltype; r->convertformat = COERCE_IMPLICIT_CAST; r->location = -1; /* Make sure the Var node has the right type ID, too */ - var->vartype = context->child_reltype; + var->vartype = appinfo->child_reltype; return (Node *) r; } } @@ -1657,16 +1670,27 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) { /* * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. */ RowExpr *rowexpr; List *fields; + RangeTblEntry *rte; - fields = (List *) copyObject(context->translated_vars); + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = (List *) copyObject(appinfo->translated_vars); rowexpr = makeNode(RowExpr); rowexpr->args = fields; rowexpr->row_typeid = var->vartype; rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = NIL; + rowexpr->colnames = copyObject(rte->eref->colnames); rowexpr->location = -1; return (Node *) rowexpr; @@ -1680,16 +1704,16 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) { CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - if (cexpr->cvarno == context->parent_relid) - cexpr->cvarno = context->child_relid; + if (cexpr->cvarno == appinfo->parent_relid) + cexpr->cvarno = appinfo->child_relid; return (Node *) cexpr; } if (IsA(node, RangeTblRef)) { RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - if (rtr->rtindex == context->parent_relid) - rtr->rtindex = context->child_relid; + if (rtr->rtindex == appinfo->parent_relid) + rtr->rtindex = appinfo->child_relid; return (Node *) rtr; } if (IsA(node, JoinExpr)) @@ -1701,8 +1725,8 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) adjust_appendrel_attrs_mutator, (void *) context); /* now fix JoinExpr's rtindex (probably never happens) */ - if (j->rtindex == context->parent_relid) - j->rtindex = context->child_relid; + if (j->rtindex == appinfo->parent_relid) + j->rtindex = appinfo->child_relid; return (Node *) j; } if (IsA(node, PlaceHolderVar)) @@ -1716,8 +1740,8 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == 0) phv->phrels = adjust_relid_set(phv->phrels, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); return (Node *) phv; } /* Shouldn't need to handle planner auxiliary nodes here */ @@ -1749,20 +1773,20 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) /* adjust relid sets too */ newinfo->clause_relids = adjust_relid_set(oldinfo->clause_relids, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); newinfo->required_relids = adjust_relid_set(oldinfo->required_relids, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); newinfo->nullable_relids = adjust_relid_set(oldinfo->nullable_relids, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); newinfo->left_relids = adjust_relid_set(oldinfo->left_relids, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); newinfo->right_relids = adjust_relid_set(oldinfo->right_relids, - context->parent_relid, - context->child_relid); + appinfo->parent_relid, + appinfo->child_relid); /* * Reset cached derivative fields, since these might need to have diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 475299daddda89ed7ec066ad9ce1b885beaa9f5c..2bffb0a651efed0f13695bb40d95243b31349ba4 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -783,13 +783,16 @@ flatten_join_alias_vars_mutator(Node *node, /* Must expand whole-row reference */ RowExpr *rowexpr; List *fields = NIL; + List *colnames = NIL; AttrNumber attnum; - ListCell *l; + ListCell *lv; + ListCell *ln; attnum = 0; - foreach(l, rte->joinaliasvars) + Assert(list_length(rte->joinaliasvars) == list_length(rte->eref->colnames)); + forboth(lv, rte->joinaliasvars, ln, rte->eref->colnames) { - newvar = (Node *) lfirst(l); + newvar = (Node *) lfirst(lv); attnum++; /* Ignore dropped columns */ if (IsA(newvar, Const)) @@ -809,12 +812,14 @@ flatten_join_alias_vars_mutator(Node *node, /* (also takes care of setting inserted_sublink if needed) */ newvar = flatten_join_alias_vars_mutator(newvar, context); fields = lappend(fields, newvar); + /* We need the names of non-dropped columns, too */ + colnames = lappend(colnames, copyObject((Node *) lfirst(ln))); } rowexpr = makeNode(RowExpr); rowexpr->args = fields; rowexpr->row_typeid = var->vartype; rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = NIL; + rowexpr->colnames = colnames; rowexpr->location = var->location; return (Node *) rowexpr; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d79576bcaa3bb2540d0e487107ede8941c032386..db63ff23711a19c43ef3cf448fbed542216416ec 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10555,6 +10555,7 @@ c_expr: columnref { $$ = $1; } RowExpr *r = makeNode(RowExpr); r->args = $1; r->row_typeid = InvalidOid; /* not analyzed yet */ + r->colnames = NIL; /* to be filled in during analysis */ r->location = @1; $$ = (Node *)r; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 698f206f169fe45b59ec19f8c46e3563477e983f..d22d8a12bac802d45479bcdb1e810996d4cad916 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1692,6 +1692,9 @@ static Node * transformRowExpr(ParseState *pstate, RowExpr *r) { RowExpr *newr = makeNode(RowExpr); + char fname[16]; + int fnum; + ListCell *lc; /* Transform the field expressions */ newr->args = transformExpressionList(pstate, r->args); @@ -1699,7 +1702,16 @@ transformRowExpr(ParseState *pstate, RowExpr *r) /* Barring later casting, we consider the type RECORD */ newr->row_typeid = RECORDOID; newr->row_format = COERCE_IMPLICIT_CAST; - newr->colnames = NIL; /* ROW() has anonymous columns */ + + /* ROW() has anonymous columns, so invent some field names */ + newr->colnames = NIL; + fnum = 1; + foreach(lc, newr->args) + { + snprintf(fname, sizeof(fname), "f%d", fnum++); + newr->colnames = lappend(newr->colnames, makeString(pstrdup(fname))); + } + newr->location = r->location; return (Node *) newr; diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index d7aabb9e4e82b1127fdc5edc2388aa805d12246e..7a54a74757e75940a238578634d183e5d303d893 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201202131 +#define CATALOG_VERSION_NO 201202141 #endif diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 9a74541d148a21640bb7449819babca44841a39b..7f27669571243f111105e6558553e814cd6ff0d3 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -256,7 +256,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate, TupleDesc tupType); extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid); extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid); -extern TupleDesc ExecTypeFromExprList(List *exprList); +extern TupleDesc ExecTypeFromExprList(List *exprList, List *namesList); extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg); typedef struct TupOutputState diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 261e7a08dd323e31049a747ea624e3e27b7d380d..50831eebf8ca3d2300c6198a8e238ccd2a122802 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -846,11 +846,15 @@ typedef struct ArrayExpr * than vice versa.) It is important not to assume that length(args) is * the same as the number of columns logically present in the rowtype. * - * colnames is NIL in a RowExpr built from an ordinary ROW() expression. - * It is provided in cases where we expand a whole-row Var into a RowExpr, - * to retain the column alias names of the RTE that the Var referenced - * (which would otherwise be very difficult to extract from the parsetree). - * Like the args list, it is one-for-one with physical fields of the rowtype. + * colnames provides field names in cases where the names can't easily be + * obtained otherwise. Names *must* be provided if row_typeid is RECORDOID. + * If row_typeid identifies a known composite type, colnames can be NIL to + * indicate the type's cataloged field names apply. Note that colnames can + * be non-NIL even for a composite type, and typically is when the RowExpr + * was created by expanding a whole-row Var. This is so that we can retain + * the column alias names of the RTE that the Var referenced (which would + * otherwise be very difficult to extract from the parsetree). Like the + * args list, colnames is one-for-one with physical fields of the rowtype. */ typedef struct RowExpr { diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 2ea3ed1e11dc4f6b782dadb40d7de2eb8e04d196..fb03acc2b4482f383a4e44087359b95316986cbb 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -52,6 +52,7 @@ extern Plan *plan_set_operations(PlannerInfo *root, double tuple_fraction, extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + AppendRelInfo *appinfo); #endif /* PREP_H */ diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index c4ebdae7e4879502db29fd849435a154165b1887..2b573511139c43be3ed469e94ddafb751432e73f 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -265,17 +265,17 @@ SELECT array_to_json(array(select 1 as a)); (1 row) SELECT array_to_json(array_agg(q),false) from (select x as b, x * 2 as c from generate_series(1,3) x) q; - array_to_json ---------------------------------------------------- - [{"f1":1,"f2":2},{"f1":2,"f2":4},{"f1":3,"f2":6}] + array_to_json +--------------------------------------------- + [{"b":1,"c":2},{"b":2,"c":4},{"b":3,"c":6}] (1 row) SELECT array_to_json(array_agg(q),true) from (select x as b, x * 2 as c from generate_series(1,3) x) q; - array_to_json -------------------- - [{"f1":1,"f2":2},+ - {"f1":2,"f2":4},+ - {"f1":3,"f2":6}] + array_to_json +----------------- + [{"b":1,"c":2},+ + {"b":2,"c":4},+ + {"b":3,"c":6}] (1 row) SELECT array_to_json(array_agg(q),false) @@ -284,9 +284,9 @@ SELECT array_to_json(array_agg(q),false) ROW(y.*,ARRAY[4,5,6])] AS z FROM generate_series(1,2) x, generate_series(4,5) y) q; - array_to_json -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"f1":"a1","f2":4,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"f1":"a1","f2":5,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},{"f1":"a2","f2":4,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"f1":"a2","f2":5,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}] + array_to_json +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},{"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}] (1 row) SELECT array_to_json(array_agg(x),false) from generate_series(5,10) x; @@ -315,12 +315,12 @@ FROM (SELECT $$a$$ || x AS b, ROW(y.*,ARRAY[4,5,6])] AS z FROM generate_series(1,2) x, generate_series(4,5) y) q; - row_to_json ------------------------------------------------------------------------ - {"f1":"a1","f2":4,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} - {"f1":"a1","f2":5,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} - {"f1":"a2","f2":4,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} - {"f1":"a2","f2":5,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} + row_to_json +-------------------------------------------------------------------- + {"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} + {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} + {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} + {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} (4 rows) SELECT row_to_json(q,true) @@ -330,20 +330,20 @@ FROM (SELECT $$a$$ || x AS b, ROW(y.*,ARRAY[4,5,6])] AS z FROM generate_series(1,2) x, generate_series(4,5) y) q; - row_to_json ------------------------------------------------------- - {"f1":"a1", + - "f2":4, + - "f3":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} - {"f1":"a1", + - "f2":5, + - "f3":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} - {"f1":"a2", + - "f2":4, + - "f3":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} - {"f1":"a2", + - "f2":5, + - "f3":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} + row_to_json +----------------------------------------------------- + {"b":"a1", + + "c":4, + + "z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} + {"b":"a1", + + "c":5, + + "z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} + {"b":"a2", + + "c":4, + + "z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]} + {"b":"a2", + + "c":5, + + "z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]} (4 rows) CREATE TEMP TABLE rows AS