From 5f8cac2b0b7fcc0d5203758e8b72dab9b709f638 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud Date: Sat, 4 Jun 2022 03:07:47 +0800 Subject: [PATCH v2 2/4] WIP Handle logical attnum A new attnum column is added to pg_attribute, to store the logical column position. The default is to have the same as the physical position. A relation is now expanded in logical order, for instance: - A plain SELECT * FROM table - A RETURNING * clause in a DML command - A COPY table FROM/TO without explicit column names Note that there's for now no possibility to declare a different logical position. psql displays a table columns information using the logical order rather the physical order, and if verbose emits an addition "Physical order" footer if the logical layout is different from the physical one. Many problems that still exist, for instance deparsing view that didn't have explicit column list and later requires it (e.g. after renaming attributes). Author: Julien Rouhaud Reviewed-by: FIXME Discussion: FIXME --- src/backend/access/common/attmap.c | 5 +- src/backend/access/common/tupconvert.c | 7 +- src/backend/access/common/tupdesc.c | 43 ++++++++ src/backend/bootstrap/bootstrap.c | 1 + src/backend/catalog/genbki.pl | 2 + src/backend/catalog/heap.c | 8 ++ src/backend/catalog/index.c | 1 + src/backend/commands/copy.c | 48 +++++++-- src/backend/commands/tablecmds.c | 11 +- src/backend/executor/execMain.c | 9 +- src/backend/nodes/copyfuncs.c | 4 + src/backend/nodes/equalfuncs.c | 2 + src/backend/nodes/outfuncs.c | 4 + src/backend/nodes/readfuncs.c | 4 + src/backend/optimizer/util/plancat.c | 2 + src/backend/optimizer/util/tlist.c | 43 ++++++++ src/backend/parser/analyze.c | 2 +- src/backend/parser/parse_clause.c | 44 +++++--- src/backend/parser/parse_coerce.c | 62 +++++++---- src/backend/parser/parse_relation.c | 142 +++++++++++++++++++++---- src/backend/parser/parse_target.c | 100 ++++++++++++----- src/backend/parser/parse_utilcmd.c | 9 +- src/backend/utils/adt/rowtypes.c | 42 ++++---- src/backend/utils/adt/ruleutils.c | 73 ++++++++++++- src/backend/utils/cache/relcache.c | 5 +- src/bin/psql/describe.c | 71 +++++++++++-- src/include/access/tupdesc.h | 2 + src/include/catalog/pg_attribute.h | 6 ++ src/include/nodes/parsenodes.h | 2 + src/include/nodes/primnodes.h | 4 +- src/include/optimizer/tlist.h | 1 + src/include/parser/parse_node.h | 5 +- src/include/parser/parse_relation.h | 2 +- src/test/regress/regress.c | 17 ++- 34 files changed, 648 insertions(+), 135 deletions(-) diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c index d84b1bcdc4..f323b4a72e 100644 --- a/src/backend/access/common/attmap.c +++ b/src/backend/access/common/attmap.c @@ -100,6 +100,7 @@ build_attrmap_by_position(TupleDesc indesc, Form_pg_attribute att = TupleDescAttr(outdesc, i); Oid atttypid; int32 atttypmod; + AttrNumber attphysnum = att->attphysnum; if (att->attisdropped) continue; /* attrMap->attnums[i] is already 0 */ @@ -125,11 +126,11 @@ build_attrmap_by_position(TupleDesc indesc, format_type_with_typemod(atttypid, atttypmod), noutcols))); - attrMap->attnums[i] = (AttrNumber) (j + 1); + attrMap->attnums[attphysnum - 1] = (AttrNumber) (j + 1); j++; break; } - if (attrMap->attnums[i] == 0) + if (attrMap->attnums[attphysnum - 1] == 0) same = false; /* we'll complain below */ } diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c index 4010e20cfb..68d05f343a 100644 --- a/src/backend/access/common/tupconvert.c +++ b/src/backend/access/common/tupconvert.c @@ -60,12 +60,15 @@ convert_tuples_by_position(TupleDesc indesc, TupleDesc outdesc, const char *msg) { + TupleDesc sorted_outdesc = CreateTupleDescCopy(outdesc); TupleConversionMap *map; int n; AttrMap *attrMap; - /* Verify compatibility and prepare attribute-number map */ - attrMap = build_attrmap_by_position(indesc, outdesc, msg); + /* Verify compatibility and prepare attribute-number map. */ + TupleDescSortByAttnum(sorted_outdesc); + attrMap = build_attrmap_by_position(indesc, sorted_outdesc, msg); + pfree(sorted_outdesc); if (attrMap == NULL) { diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 409a93ccdf..e9f9afbd0c 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -128,6 +128,9 @@ CreateTupleDescCopy(TupleDesc tupdesc) { Form_pg_attribute att = TupleDescAttr(desc, i); + /* Caller should have provided it */ + Assert(AttributeNumberIsValid(att->attnum)); + att->attnotnull = false; att->atthasdef = false; att->atthasmissing = false; @@ -285,6 +288,8 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, /* * Aside from updating the attno, we'd better reset attcacheoff. * + * The attnum should however be preserved. + * * XXX Actually, to be entirely safe we'd need to reset the attcacheoff of * all following columns in dst as well. Current usage scenarios don't * require that though, because all following columns will get initialized @@ -620,6 +625,8 @@ TupleDescInitEntry(TupleDesc desc, att->atttypmod = typmod; att->attphysnum = attributeNumber; + /* FIXME should we put 0 and let caller always set it as needed? */ + att->attnum = attributeNumber; att->attndims = attdim; att->attnotnull = false; @@ -765,6 +772,40 @@ TupleDescInitEntryCollation(TupleDesc desc, TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid; } +/* + * cmp_attnum + * + * Comparator for sorting TupleDesc attributes by they logical position + * (attnum). + */ +static int +cmp_attnum(const void *a, const void *b) +{ + FormData_pg_attribute *atta = (FormData_pg_attribute *) a; + FormData_pg_attribute *attb = (FormData_pg_attribute *) b; + + return (atta->attnum > attb->attnum) ? 1 + : (atta->attnum == attb->attnum) ? 0 + : -1; +} + +/* + * TupleDescSortByAttnum + * + * Sort the attributes by their logical position (attnum) rather than their + * physical position (attphysnum). + * This function will modify the given TupleDesc, so caller should make a copy + * first if needed. + */ +void +TupleDescSortByAttnum(TupleDesc desc) +{ + if (desc->natts <= 1) + return; + + qsort(desc->attrs, desc->natts, sizeof(FormData_pg_attribute), cmp_attnum); +} + /* * BuildDescForRelation @@ -828,6 +869,8 @@ BuildDescForRelation(List *schema) TupleDescInitEntry(desc, attphysnum, attname, atttypid, atttypmod, attdim); att = TupleDescAttr(desc, attphysnum - 1); + /* FIXME - change me when there's a syntax to specify it */ + att->attnum = attphysnum; /* Override TupleDescInitEntry's settings as requested */ TupleDescInitEntryCollation(desc, attphysnum, attcollation); diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 37107dc12b..767bdd5477 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -518,6 +518,7 @@ DefineAttr(char *name, char *type, int attphysnum, int nullness) namestrcpy(&attrtypes[attphysnum]->attname, name); elog(DEBUG4, "column %s %s", NameStr(attrtypes[attphysnum]->attname), type); attrtypes[attphysnum]->attphysnum = attphysnum + 1; + attrtypes[attphysnum]->attnum = attphysnum + 1; typeoid = gettype(type); diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index f1d33dd949..cc23c5e909 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -852,6 +852,7 @@ sub gen_pg_attribute $attphysnum++; my %row; $row{attphysnum} = $attphysnum; + $row{attnum} = $attphysnum; $row{attrelid} = $table->{relation_oid}; morph_row_for_pgattr(\%row, $schema, $attr, $priorfixedwidth); @@ -888,6 +889,7 @@ sub gen_pg_attribute $attphysnum--; my %row; $row{attphysnum} = $attphysnum; + $row{attnum} = $attphysnum; $row{attrelid} = $table->{relation_oid}; $row{attstattarget} = '0'; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index ed9929e67a..1ce0517e57 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -732,6 +732,14 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget); slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen); slot[slotCount]->tts_values[Anum_pg_attribute_attphysnum - 1] = Int16GetDatum(attrs->attphysnum); + /* + * If caller provided a specific attnum use it, otherwise just fallback + * on attphysnum + */ + if (AttributeNumberIsValid(attrs->attnum)) + slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum); + else + slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attphysnum); slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims); slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1); slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index c514e3b622..153916e44c 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -311,6 +311,7 @@ ConstructTupleDescriptor(Relation heapRelation, MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE); to->attphysnum = i + 1; + to->attnum = to->attphysnum; to->attstattarget = -1; to->attcacheoff = -1; to->attislocal = true; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 6b15837dba..7ee0703ed0 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -65,6 +65,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, int stmt_location, int stmt_len, uint64 *processed) { + List *attlist = stmt->attlist; bool is_from = stmt->is_from; bool pipe = (stmt->filename == NULL); Relation rel; @@ -147,7 +148,34 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, } tupDesc = RelationGetDescr(rel); - attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist); + + /* If no column list has been provided, generate one using the logical + * attribute numbers rather than physical order. + * We don't include generated columns in the generated full list and we + * don't allow them to be specified explicitly. + */ + if (attlist == NIL) + { + TupleDesc tmp = CreateTupleDescCopyConstr(tupDesc); + TupleDescSortByAttnum(tmp); + + for (int i = 0; i < tmp->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tmp, i); + + if (attr->attisdropped) + continue; + if (attr->attgenerated) + continue; + + attlist = lappend(attlist, + makeString(pstrdup(NameStr(attr->attname)))); + } + + pfree(tmp); + } + + attnums = CopyGetAttnums(tupDesc, rel, attlist); foreach(cur, attnums) { int attno = lfirst_int(cur) - @@ -199,8 +227,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, * In the case that columns are specified in the attribute list, * create a ColumnRef and ResTarget for each column and add them * to the target list for the resulting SELECT statement. + * + * FIXME - should be dead code */ - if (!stmt->attlist) + if (!attlist) { cr = makeNode(ColumnRef); cr->fields = list_make1(makeNode(A_Star)); @@ -218,7 +248,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, { ListCell *lc; - foreach(lc, stmt->attlist) + foreach(lc, attlist) { /* * Build the ColumnRef for each column. The ColumnRef @@ -294,7 +324,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, cstate = BeginCopyFrom(pstate, rel, whereClause, stmt->filename, stmt->is_program, - NULL, stmt->attlist, stmt->options); + NULL, attlist, stmt->options); *processed = CopyFrom(cstate); /* copy from file to database */ EndCopyFrom(cstate); } @@ -304,7 +334,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, cstate = BeginCopyTo(pstate, rel, query, relid, stmt->filename, stmt->is_program, - stmt->attlist, stmt->options); + attlist, stmt->options); *processed = DoCopyTo(cstate); /* copy from database to file */ EndCopyTo(cstate); } @@ -724,11 +754,13 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) for (i = 0; i < attr_count; i++) { - if (TupleDescAttr(tupDesc, i)->attisdropped) + Form_pg_attribute attr = TupleDescAttr(tupDesc, i); + + if (attr->attisdropped) continue; - if (TupleDescAttr(tupDesc, i)->attgenerated) + if (attr->attgenerated) continue; - attnums = lappend_int(attnums, i + 1); + attnums = lappend_int(attnums, attr->attphysnum); } } else diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ff9a9fc911..007e355d9d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2504,7 +2504,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence, aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind), RelationGetRelationName(relation)); - tupleDesc = RelationGetDescr(relation); + /* + * Make a copy of the tupledesc and sort it by logical attnum, as this + * is the expected order for inherited relations. + * FIXME - should keep the original attphysnum and compute an attnum + * instead? + */ + tupleDesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); + TupleDescSortByAttnum(tupleDesc); constr = tupleDesc->constr; /* @@ -6815,6 +6822,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attstattarget = (newattnum > 0) ? -1 : 0; attribute.attlen = tform->typlen; attribute.attphysnum = newattnum; + /* FIXME - change me when there's a syntax to specify it */ + attribute.attnum = newattnum; attribute.attndims = list_length(colDef->typeName->arrayBounds); attribute.atttypmod = typmod; attribute.attbyval = tform->typbyval; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 045c25e634..4912bb96c5 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2245,6 +2245,10 @@ ExecBuildSlotValueDescription(Oid reloid, /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); + /* Sort the tuple desc to emit value in logical order */ + tupdesc = CreateTupleDescCopy(tupdesc); + TupleDescSortByAttnum(tupdesc); + for (i = 0; i < tupdesc->natts; i++) { bool column_perm = false; @@ -2282,7 +2286,7 @@ ExecBuildSlotValueDescription(Oid reloid, if (table_perm || column_perm) { - if (slot->tts_isnull[i]) + if (slot->tts_isnull[att->attphysnum - 1]) val = "null"; else { @@ -2291,7 +2295,8 @@ ExecBuildSlotValueDescription(Oid reloid, getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena); - val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); + val = OidOutputFunctionCall(foutoid, + slot->tts_values[att->attphysnum - 1]); } if (write_comma) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 51d630fa89..7ee1876361 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1455,6 +1455,7 @@ _copyVar(const Var *from) COPY_SCALAR_FIELD(varno); COPY_SCALAR_FIELD(varattno); + COPY_SCALAR_FIELD(varnum); COPY_SCALAR_FIELD(vartype); COPY_SCALAR_FIELD(vartypmod); COPY_SCALAR_FIELD(varcollid); @@ -1814,6 +1815,7 @@ _copyFieldSelect(const FieldSelect *from) COPY_NODE_FIELD(arg); COPY_SCALAR_FIELD(fieldnum); + COPY_SCALAR_FIELD(fieldlognum); COPY_SCALAR_FIELD(resulttype); COPY_SCALAR_FIELD(resulttypmod); COPY_SCALAR_FIELD(resultcollid); @@ -2959,6 +2961,8 @@ _copyRangeTblEntry(const RangeTblEntry *from) COPY_NODE_FIELD(join_using_alias); COPY_NODE_FIELD(functions); COPY_SCALAR_FIELD(funcordinality); + COPY_SCALAR_FIELD(nummappings); + COPY_POINTER_FIELD(mappings, from->nummappings * sizeof(AttrNumber)); COPY_NODE_FIELD(tablefunc); COPY_NODE_FIELD(values_lists); COPY_STRING_FIELD(ctename); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index e747e1667d..e1f9cb4f41 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -551,6 +551,7 @@ _equalFieldSelect(const FieldSelect *a, const FieldSelect *b) { COMPARE_NODE_FIELD(arg); COMPARE_SCALAR_FIELD(fieldnum); + COMPARE_SCALAR_FIELD(fieldlognum); COMPARE_SCALAR_FIELD(resulttype); COMPARE_SCALAR_FIELD(resulttypmod); COMPARE_SCALAR_FIELD(resultcollid); @@ -3141,6 +3142,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) COMPARE_NODE_FIELD(join_using_alias); COMPARE_NODE_FIELD(functions); COMPARE_SCALAR_FIELD(funcordinality); + COMPARE_POINTER_FIELD(mappings, a->nummappings * sizeof(AttrNumber)); COMPARE_NODE_FIELD(tablefunc); COMPARE_NODE_FIELD(values_lists); COMPARE_STRING_FIELD(ctename); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index ce12915592..d687c1e013 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1143,6 +1143,7 @@ _outVar(StringInfo str, const Var *node) WRITE_INT_FIELD(varno); WRITE_INT_FIELD(varattno); + WRITE_INT_FIELD(varnum); WRITE_OID_FIELD(vartype); WRITE_INT_FIELD(vartypmod); WRITE_OID_FIELD(varcollid); @@ -1421,6 +1422,7 @@ _outFieldSelect(StringInfo str, const FieldSelect *node) WRITE_NODE_FIELD(arg); WRITE_INT_FIELD(fieldnum); + WRITE_INT_FIELD(fieldlognum); WRITE_OID_FIELD(resulttype); WRITE_INT_FIELD(resulttypmod); WRITE_OID_FIELD(resultcollid); @@ -3460,6 +3462,8 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) case RTE_FUNCTION: WRITE_NODE_FIELD(functions); WRITE_BOOL_FIELD(funcordinality); + WRITE_INT_FIELD(nummappings); + WRITE_ATTRNUMBER_ARRAY(mappings, node->nummappings); break; case RTE_TABLEFUNC: WRITE_NODE_FIELD(tablefunc); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6a05b69415..35f0352826 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -619,6 +619,7 @@ _readVar(void) READ_INT_FIELD(varno); READ_INT_FIELD(varattno); + READ_INT_FIELD(varnum); READ_OID_FIELD(vartype); READ_INT_FIELD(vartypmod); READ_OID_FIELD(varcollid); @@ -939,6 +940,7 @@ _readFieldSelect(void) READ_NODE_FIELD(arg); READ_INT_FIELD(fieldnum); + READ_INT_FIELD(fieldlognum); READ_OID_FIELD(resulttype); READ_INT_FIELD(resulttypmod); READ_OID_FIELD(resultcollid); @@ -1686,6 +1688,8 @@ _readRangeTblEntry(void) case RTE_FUNCTION: READ_NODE_FIELD(functions); READ_BOOL_FIELD(funcordinality); + READ_INT_FIELD(nummappings); + READ_ATTRNUMBER_ARRAY(mappings, local_node->nummappings); break; case RTE_TABLEFUNC: READ_NODE_FIELD(tablefunc); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 5012bfe142..07baf4c9eb 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -41,6 +41,7 @@ #include "optimizer/optimizer.h" #include "optimizer/plancat.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "partitioning/partdesc.h" @@ -1698,6 +1699,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) att_tup->atttypmod, att_tup->attcollation, 0); + var->varnum = att_tup->attnum; tlist = lappend(tlist, makeTargetEntry((Expr *) var, diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index fe9a9d7d89..82f28c071b 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -1056,6 +1056,49 @@ split_pathtarget_at_srfs(PlannerInfo *root, } } +/* + * Comparator for sorting list of TargetEntries by their logical order. + * + * The target entry's expr should be either a Var or a FieldSelect. + */ +int +cmp_targetentry_logical_order(const ListCell *a, const ListCell *b) +{ + TargetEntry *ta = lfirst_node(TargetEntry, a); + TargetEntry *tb = lfirst_node(TargetEntry, b); + + if (IsA(ta->expr, Var)) + { + Var *va; + Var *vb; + + Assert(IsA(tb->expr, Var)); + + va = (Var *) ta->expr; + vb = (Var *) tb->expr; + + if (va->varno != vb->varno) + return (va->varno > vb->varno) ? 1 : (va->varno == vb->varno) ? 0 : -1; + return (va->varnum > vb->varnum) ? 1 : (va->varnum == vb->varnum) ? 0 : -1; + } + else + { + FieldSelect *fa; + FieldSelect *fb; + + Assert(IsA(ta->expr, FieldSelect) && IsA(tb->expr, FieldSelect)); + + fa = (FieldSelect *) ta->expr; + fb = (FieldSelect *) tb->expr; + + Assert(AttributeNumberIsValid(fa->fieldlognum) && + AttributeNumberIsValid(fb->fieldlognum)); + + return (fa->fieldlognum > fb->fieldlognum) ? 1 : (fa->fieldlognum == fb->fieldlognum) ? 0 : -1; + } + +} + /* * Recursively examine expressions for split_pathtarget_at_srfs. * diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 5ced631957..bf811b3f06 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1589,7 +1589,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) * Generate a targetlist as though expanding "*" */ Assert(pstate->p_next_resno == 1); - qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, true, -1); + qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, true, false, -1); /* * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d1b0080268..b4d4345b13 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -53,6 +53,7 @@ static int extractRemainingColumns(ParseNamespaceColumn *src_nscolumns, + AttrNumber *mappings, List *src_colnames, List **src_colnos, List **res_colnames, List **res_colvars, @@ -250,6 +251,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation, */ static int extractRemainingColumns(ParseNamespaceColumn *src_nscolumns, + AttrNumber *mappings, List *src_colnames, List **src_colnos, List **res_colnames, List **res_colvars, @@ -273,11 +275,18 @@ extractRemainingColumns(ParseNamespaceColumn *src_nscolumns, } attphysnum = 0; - foreach(lc, src_colnames) + for (int i = 1; i <= list_length(src_colnames); i++) { - char *colname = strVal(lfirst(lc)); + char *colname; + + if (!AttributeNumberIsValid(mappings[i])) + continue; + + attphysnum = mappings[i]; + lc = list_nth_cell(src_colnames, attphysnum - 1); + colname = strVal(lfirst(lc)); + Assert(colname[0] != '\0'); - attphysnum++; /* Non-dropped and not already merged? */ if (colname[0] != '\0' && !bms_is_member(attphysnum, prevcols)) { @@ -1163,6 +1172,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, ParseNamespaceColumn *l_nscolumns, *r_nscolumns, *res_nscolumns; + AttrNumber *l_mappings, *r_mappings; int res_colindex; bool lateral_ok; int sv_namespace_length; @@ -1223,8 +1233,10 @@ transformFromClauseItem(ParseState *pstate, Node *n, */ l_nscolumns = l_nsitem->p_nscolumns; l_colnames = l_nsitem->p_names->colnames; + l_mappings = l_nsitem->p_mappings; r_nscolumns = r_nsitem->p_nscolumns; r_colnames = r_nsitem->p_names->colnames; + r_mappings = r_nsitem->p_mappings; /* * Natural join does not explicitly specify columns; must generate @@ -1238,22 +1250,27 @@ transformFromClauseItem(ParseState *pstate, Node *n, if (j->isNatural) { List *rlist = NIL; - ListCell *lx, - *rx; Assert(j->usingClause == NIL); /* shouldn't have USING() too */ - foreach(lx, l_colnames) + for (int i = 1; i <= list_length(l_colnames); i++) { - char *l_colname = strVal(lfirst(lx)); + char *l_colname; String *m_name = NULL; - if (l_colname[0] == '\0') + if (!AttributeNumberIsValid(l_mappings[i])) continue; /* ignore dropped columns */ - foreach(rx, r_colnames) + l_colname = strVal(list_nth(l_colnames, l_mappings[i] - 1)); + + for (int j = 1; j <= list_length(r_colnames); j++) { - char *r_colname = strVal(lfirst(rx)); + char *r_colname; + + if (!AttributeNumberIsValid(r_mappings[j])) + continue; /* ignore dropped columns */ + + r_colname = strVal(list_nth(r_colnames, r_mappings[j] - 1)); if (strcmp(l_colname, r_colname) == 0) { @@ -1436,11 +1453,13 @@ transformFromClauseItem(ParseState *pstate, Node *n, /* Add remaining columns from each side to the output columns */ res_colindex += - extractRemainingColumns(l_nscolumns, l_colnames, &l_colnos, + extractRemainingColumns(l_nscolumns, l_mappings, + l_colnames, &l_colnos, &res_colnames, &res_colvars, res_nscolumns + res_colindex); res_colindex += - extractRemainingColumns(r_nscolumns, r_colnames, &r_colnos, + extractRemainingColumns(r_nscolumns, r_mappings, + r_colnames, &r_colnos, &res_colnames, &res_colvars, res_nscolumns + res_colindex); @@ -1569,6 +1588,7 @@ buildVarFromNSColumn(ParseNamespaceColumn *nscol) nscol->p_varcollid, 0); /* makeVar doesn't offer parameters for these, so set by hand: */ + var->varnum = nscol->p_varnum; var->varnosyn = nscol->p_varnosyn; var->varattnosyn = nscol->p_varattnosyn; return var; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index c4e958e4aa..e2a7fc7245 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -1018,12 +1018,12 @@ coerce_record_to_complex(ParseState *pstate, Node *node, RowExpr *rowexpr; Oid baseTypeId; int32 baseTypeMod = -1; - TupleDesc tupdesc; + TupleDesc tupdesc, sorted_tupdesc; List *args = NIL; List *newargs; int i; int ucolno; - ListCell *arg; + AttrNumber *mappings; if (node && IsA(node, RowExpr)) { @@ -1059,10 +1059,46 @@ coerce_record_to_complex(ParseState *pstate, Node *node, baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); tupdesc = lookup_rowtype_tupdesc(baseTypeId, baseTypeMod); + /* + * Generated an "attnum / position in args" mappings, since there's no + * guarantee of a 1/1 mapping due to dropped columns. + */ + sorted_tupdesc = CreateTupleDescCopy(tupdesc); + TupleDescSortByAttnum(sorted_tupdesc); + mappings = palloc0(sizeof(AttrNumber) * (sorted_tupdesc->natts + 1)); + /* use a 1-based position to be able to rely on AttributeNumberIsValid. */ + ucolno = 1; + for (i = 0; i < sorted_tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + continue; + + mappings[attr->attphysnum] = ucolno++; + } + pfree(sorted_tupdesc); + + if (list_length(args) < (ucolno - 1)) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too few columns."), + parser_coercion_errposition(pstate, location, node))); + else if (list_length(args) > (ucolno - 1)) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too many columns."), + parser_coercion_errposition(pstate, location, node))); + /* Process the fields */ newargs = NIL; ucolno = 1; - arg = list_head(args); for (i = 0; i < tupdesc->natts; i++) { Node *expr; @@ -1082,15 +1118,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node, continue; } - if (arg == NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(RECORDOID), - format_type_be(targetTypeId)), - errdetail("Input has too few columns."), - parser_coercion_errposition(pstate, location, node))); - expr = (Node *) lfirst(arg); + Assert(AttributeNumberIsValid(mappings[attr->attphysnum])); + expr = list_nth(args, mappings[attr->attnum] - 1); exprtype = exprType(expr); cexpr = coerce_to_target_type(pstate, @@ -1113,16 +1142,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node, parser_coercion_errposition(pstate, location, expr))); newargs = lappend(newargs, cexpr); ucolno++; - arg = lnext(args, arg); } - if (arg != NULL) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(RECORDOID), - format_type_be(targetTypeId)), - errdetail("Input has too many columns."), - parser_coercion_errposition(pstate, location, node))); ReleaseTupleDesc(tupdesc); diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 27c3abb7ec..e1ffd3d683 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -26,6 +26,7 @@ #include "funcapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/tlist.h" #include "parser/parse_enr.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" @@ -733,6 +734,7 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem, nscol->p_varcollid, sublevels_up); /* makeVar doesn't offer parameters for these, so set them by hand: */ + var->varnum = nscol->p_varnum; var->varnosyn = nscol->p_varnosyn; var->varattnosyn = nscol->p_varattnosyn; } @@ -1126,28 +1128,65 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) { int maxattrs = tupdesc->natts; List *aliaslist; - ListCell *aliaslc; int numaliases; int varattno; int numdropped = 0; + int *mappings; Assert(eref->colnames == NIL); if (alias) { aliaslist = alias->colnames; - aliaslc = list_head(aliaslist); numaliases = list_length(aliaslist); /* We'll rebuild the alias colname list */ alias->colnames = NIL; + mappings = palloc0(sizeof(int) * (maxattrs + 1)); } else { aliaslist = NIL; - aliaslc = NULL; numaliases = 0; } + /* + * Build a physical/logical position mapping to emit aliases in logical + * order. + */ + if (numaliases > 0) + { + TupleDesc sorted = CreateTupleDescCopy(tupdesc); + int i = 1; + + TupleDescSortByAttnum(sorted); + + for (varattno = 0; varattno < maxattrs; varattno++) + { + Form_pg_attribute attr = TupleDescAttr(sorted, varattno); + + if (attr->attisdropped) + continue; + + /* + * We record a 1-based position mapping to be able to use + * AttributeNumberIsValid. + */ + mappings[attr->attphysnum] = i++; + + /* We're done if we already mapped all the provided aliases. */ + if (i > numaliases) + break; + } + pfree(sorted); + + /* Too many user-supplied aliases? */ + if (i <= numaliases) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("table \"%s\" has %d columns available but %d columns specified", + eref->aliasname, i - 1, numaliases))); + } + for (varattno = 0; varattno < maxattrs; varattno++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno); @@ -1157,15 +1196,15 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) { /* Always insert an empty string for a dropped column */ attrname = makeString(pstrdup("")); - if (aliaslc) - alias->colnames = lappend(alias->colnames, attrname); numdropped++; + if (aliaslist && varattno - numdropped > numaliases) + alias->colnames = lappend(alias->colnames, attrname); } - else if (aliaslc) + else if (aliaslist && AttributeNumberIsValid(mappings[attr->attphysnum])) { - /* Use the next user-supplied alias */ - attrname = lfirst_node(String, aliaslc); - aliaslc = lnext(aliaslist, aliaslc); + /* Use the next user-supplied alias, in logical order */ + attrname = list_nth_node(String, aliaslist, + mappings[attr->attphysnum] - 1); alias->colnames = lappend(alias->colnames, attrname); } else @@ -1176,13 +1215,6 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) eref->colnames = lappend(eref->colnames, attrname); } - - /* Too many user-supplied aliases? */ - if (aliaslc) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("table \"%s\" has %d columns available but %d columns specified", - eref->aliasname, maxattrs - numdropped, numaliases))); } /* @@ -1242,6 +1274,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc) { ParseNamespaceItem *nsitem; ParseNamespaceColumn *nscolumns; + AttrNumber *mappings; int maxattrs = tupdesc->natts; int varattno; @@ -1251,6 +1284,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc) /* extract per-column data from the tupdesc */ nscolumns = (ParseNamespaceColumn *) palloc0(maxattrs * sizeof(ParseNamespaceColumn)); + mappings = (AttrNumber *) palloc((maxattrs + 1) * sizeof(AttrNumber)); for (varattno = 0; varattno < maxattrs; varattno++) { @@ -1258,15 +1292,24 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc) /* For a dropped column, just leave the entry as zeroes */ if (attr->attisdropped) + { + mappings[attr->attnum] = InvalidAttrNumber; continue; + } + + mappings[attr->attnum] = attr->attphysnum; + nscolumns[varattno].p_varno = rtindex; nscolumns[varattno].p_varattno = varattno + 1; + nscolumns[varattno].p_varattno = attr->attphysnum; + nscolumns[varattno].p_varnum = attr->attnum; nscolumns[varattno].p_vartype = attr->atttypid; nscolumns[varattno].p_vartypmod = attr->atttypmod; nscolumns[varattno].p_varcollid = attr->attcollation; nscolumns[varattno].p_varnosyn = rtindex; nscolumns[varattno].p_varattnosyn = varattno + 1; + nscolumns[varattno].p_varattnosyn = attr->attphysnum; } /* ... and build the nsitem */ @@ -1275,6 +1318,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc) nsitem->p_rte = rte; nsitem->p_rtindex = rtindex; nsitem->p_nscolumns = nscolumns; + nsitem->p_mappings = mappings; /* set default visibility flags; might get changed later */ nsitem->p_rel_visible = true; nsitem->p_cols_visible = true; @@ -1300,6 +1344,7 @@ buildNSItemFromLists(RangeTblEntry *rte, Index rtindex, { ParseNamespaceItem *nsitem; ParseNamespaceColumn *nscolumns; + AttrNumber *mappings; int maxattrs = list_length(coltypes); int varattno; ListCell *lct; @@ -1315,6 +1360,7 @@ buildNSItemFromLists(RangeTblEntry *rte, Index rtindex, /* extract per-column data from the lists */ nscolumns = (ParseNamespaceColumn *) palloc0(maxattrs * sizeof(ParseNamespaceColumn)); + mappings = (AttrNumber *) palloc((maxattrs + 1) * sizeof(AttrNumber)); varattno = 0; forthree(lct, coltypes, @@ -1323,11 +1369,18 @@ buildNSItemFromLists(RangeTblEntry *rte, Index rtindex, { nscolumns[varattno].p_varno = rtindex; nscolumns[varattno].p_varattno = varattno + 1; + if (rte->rtekind == RTE_RELATION) + nscolumns[varattno].p_varnum = varattno + 1; + else + nscolumns[varattno].p_varnum = InvalidAttrNumber; nscolumns[varattno].p_vartype = lfirst_oid(lct); nscolumns[varattno].p_vartypmod = lfirst_int(lcm); nscolumns[varattno].p_varcollid = lfirst_oid(lcc); nscolumns[varattno].p_varnosyn = rtindex; nscolumns[varattno].p_varattnosyn = varattno + 1; + + mappings[varattno + 1] = varattno + 1; + varattno++; } @@ -1337,6 +1390,7 @@ buildNSItemFromLists(RangeTblEntry *rte, Index rtindex, nsitem->p_rte = rte; nsitem->p_rtindex = rtindex; nsitem->p_nscolumns = nscolumns; + nsitem->p_mappings = mappings; /* set default visibility flags; might get changed later */ nsitem->p_rel_visible = true; nsitem->p_cols_visible = true; @@ -1408,6 +1462,9 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode) * * Note: formerly this checked for refname conflicts, but that's wrong. * Caller is responsible for checking for conflicts in the appropriate scope. + * + * Note also that the ParseNamespaceItem is generated using the range table's + * logical order (attnum) rather than physical order (attphysnum). */ ParseNamespaceItem * addRangeTableEntry(ParseState *pstate, @@ -1447,7 +1504,7 @@ addRangeTableEntry(ParseState *pstate, /* * Build the list of effective column names using user-supplied aliases - * and/or actual column names. + * and/or actual column names, using the range table logical order. */ rte->eref = makeAlias(refname, NIL); buildRelationAliases(rel->rd_att, alias, rte->eref); @@ -1688,6 +1745,7 @@ addRangeTableEntryForFunction(ParseState *pstate, Alias *alias = rangefunc->alias; Alias *eref; char *aliasname; + AttrNumber *mappings; int nfuncs = list_length(funcexprs); TupleDesc *functupdescs; TupleDesc tupdesc; @@ -1908,6 +1966,8 @@ addRangeTableEntryForFunction(ParseState *pstate, */ if (nfuncs > 1 || rangefunc->ordinality) { + int attnumoffset = 0; + if (rangefunc->ordinality) totalatts++; @@ -1917,7 +1977,15 @@ addRangeTableEntryForFunction(ParseState *pstate, for (i = 0; i < nfuncs; i++) { for (j = 1; j <= functupdescs[i]->natts; j++) + { TupleDescCopyEntry(tupdesc, ++natts, functupdescs[i], j); + /* + * TupleDescCopyEntry preserved the original attnum, we still + * need to offset it to take into account previous tuple descs. + */ + tupdesc->attrs[natts - 1].attnum += attnumoffset; + } + attnumoffset += functupdescs[i]->natts; } /* Add the ordinality column if needed */ @@ -1940,6 +2008,12 @@ addRangeTableEntryForFunction(ParseState *pstate, tupdesc = functupdescs[0]; } + mappings = palloc(tupdesc->natts * sizeof(AttrNumber)); + for (i = 0; i < tupdesc->natts; i++) + mappings[i] = TupleDescAttr(tupdesc, i)->attnum; + rte->nummappings = tupdesc->natts; + rte->mappings = mappings; + /* Use the tupdesc while assigning column aliases for the RTE */ buildRelationAliases(tupdesc, alias, eref); @@ -2169,6 +2243,7 @@ addRangeTableEntryForJoin(ParseState *pstate, Alias *eref; int numaliases; ParseNamespaceItem *nsitem; + AttrNumber *mappings; Assert(pstate != NULL); @@ -2232,6 +2307,10 @@ addRangeTableEntryForJoin(ParseState *pstate, */ pstate->p_rtable = lappend(pstate->p_rtable, rte); + mappings = palloc((list_length(colnames) + 1) * sizeof(AttrNumber)); + for (int i = 1; i <= list_length(colnames); i++) + mappings[i] = i; + /* * Build a ParseNamespaceItem, but don't add it to the pstate's namespace * list --- caller must do that if appropriate. @@ -2241,6 +2320,7 @@ addRangeTableEntryForJoin(ParseState *pstate, nsitem->p_rte = rte; nsitem->p_rtindex = list_length(pstate->p_rtable); nsitem->p_nscolumns = nscolumns; + nsitem->p_mappings = mappings; /* set default visibility flags; might get changed later */ nsitem->p_rel_visible = true; nsitem->p_cols_visible = true; @@ -3099,6 +3179,7 @@ expandNSItemVars(ParseNamespaceItem *nsitem, nscol->p_varcollid, sublevels_up); /* makeVar doesn't offer parameters for these, so set by hand: */ + var->varnum = nscol->p_varnum; var->varnosyn = nscol->p_varnosyn; var->varattnosyn = nscol->p_varattnosyn; var->location = location; @@ -3127,13 +3208,15 @@ expandNSItemVars(ParseNamespaceItem *nsitem, */ List * expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, - int sublevels_up, bool require_col_privs, int location) + int sublevels_up, bool require_col_privs, bool logical_order, + int location) { RangeTblEntry *rte = nsitem->p_rte; List *names, *vars; ListCell *name, - *var; + *var, + *lc; List *te_list = NIL; vars = expandNSItemVars(nsitem, sublevels_up, location, &names); @@ -3149,14 +3232,22 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, if (rte->rtekind == RTE_RELATION) rte->requiredPerms |= ACL_SELECT; + /* JOIN are already expanded in the expected order. */ + if (logical_order && nsitem->p_rte->rtekind == RTE_JOIN) + logical_order = false; + forboth(name, names, var, vars) { char *label = strVal(lfirst(name)); Var *varnode = (Var *) lfirst(var); TargetEntry *te; + /* + * We don't assign the resno here, as we may have to sort the list by + * logical attnum first. + */ te = makeTargetEntry((Expr *) varnode, - (AttrNumber) pstate->p_next_resno++, + 0, label, false); te_list = lappend(te_list, te); @@ -3168,6 +3259,17 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, } } + if (logical_order) + list_sort(te_list, cmp_targetentry_logical_order); + + /* Assign the resnos now that the list is in the final order */ + foreach(lc, te_list) + { + TargetEntry *te = (TargetEntry *) lfirst(lc); + + te->resno = (AttrNumber) pstate->p_next_resno++; + } + Assert(name == NULL && var == NULL); /* lists not the same length? */ return te_list; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index cff1cda48d..2b887ee4d9 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -20,6 +20,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" @@ -48,15 +49,18 @@ static Node *transformAssignmentSubscripts(ParseState *pstate, CoercionContext ccontext, int location); static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, - bool make_target_entry); -static List *ExpandAllTables(ParseState *pstate, int location); + bool make_target_entry, + bool logical_order); +static List *ExpandAllTables(ParseState *pstate, bool logical_order, + int location); static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, - bool make_target_entry, ParseExprKind exprKind); + bool make_target_entry, bool logical_order, + ParseExprKind exprKind); static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, int sublevels_up, int location, - bool make_target_entry); + bool make_target_entry, bool logical_order); static List *ExpandRowReference(ParseState *pstate, Node *expr, - bool make_target_entry); + bool make_target_entry, bool logical_order); static int FigureColnameInternal(Node *node, char **name); @@ -150,10 +154,12 @@ transformTargetList(ParseState *pstate, List *targetlist, if (IsA(llast(cref->fields), A_Star)) { - /* It is something.*, expand into multiple items */ + /* + $ It is something.*, expand into multiple items + */ p_target = list_concat(p_target, - ExpandColumnRefStar(pstate, - cref, + ExpandColumnRefStar(pstate, cref, + true, true)); continue; } @@ -169,6 +175,7 @@ transformTargetList(ParseState *pstate, List *targetlist, ExpandIndirectionStar(pstate, ind, true, + true, exprKind)); continue; } @@ -243,7 +250,7 @@ transformExpressionList(ParseState *pstate, List *exprlist, /* It is something.*, expand into multiple items */ result = list_concat(result, ExpandColumnRefStar(pstate, cref, - false)); + false, false)); continue; } } @@ -256,7 +263,8 @@ transformExpressionList(ParseState *pstate, List *exprlist, /* It is something.*, expand into multiple items */ result = list_concat(result, ExpandIndirectionStar(pstate, ind, - false, exprKind)); + false, false, + exprKind)); continue; } } @@ -1014,6 +1022,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) if (cols == NIL) { + TupleDesc tupdesc; + /* * Generate default column list for INSERT. */ @@ -1021,12 +1031,15 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) int i; + tupdesc = CreateTupleDescCopy(pstate->p_target_relation->rd_att); + TupleDescSortByAttnum(tupdesc); + for (i = 0; i < numcol; i++) { ResTarget *col; Form_pg_attribute attr; - attr = TupleDescAttr(pstate->p_target_relation->rd_att, i); + attr = TupleDescAttr(tupdesc, i); if (attr->attisdropped) continue; @@ -1037,8 +1050,10 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) col->val = NULL; col->location = -1; cols = lappend(cols, col); - *attrnos = lappend_int(*attrnos, i + 1); + *attrnos = lappend_int(*attrnos, attr->attphysnum); } + + pfree(tupdesc); } else { @@ -1114,7 +1129,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) */ static List * ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, - bool make_target_entry) + bool make_target_entry, bool logical_order) { List *fields = cref->fields; int numnames = list_length(fields); @@ -1130,7 +1145,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, * need not handle the make_target_entry==false case here. */ Assert(make_target_entry); - return ExpandAllTables(pstate, cref->location); + return ExpandAllTables(pstate, logical_order, cref->location); } else { @@ -1169,7 +1184,8 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, node = pstate->p_pre_columnref_hook(pstate, cref); if (node != NULL) - return ExpandRowReference(pstate, node, make_target_entry); + return ExpandRowReference(pstate, node, make_target_entry, + logical_order); } switch (numnames) @@ -1234,7 +1250,8 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, errmsg("column reference \"%s\" is ambiguous", NameListToString(cref->fields)), parser_errposition(pstate, cref->location))); - return ExpandRowReference(pstate, node, make_target_entry); + return ExpandRowReference(pstate, node, make_target_entry, + logical_order); } } @@ -1270,7 +1287,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, * OK, expand the nsitem into fields. */ return ExpandSingleTable(pstate, nsitem, levels_up, cref->location, - make_target_entry); + make_target_entry, logical_order); } } @@ -1283,10 +1300,13 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, * that would include input tables of aliasless JOINs, NEW/OLD pseudo-entries, * etc. * + * If logical_order is true, the expansion is done using the relation logical + * order (attnum) rather than physical order. + * * The referenced relations/columns are marked as requiring SELECT access. */ static List * -ExpandAllTables(ParseState *pstate, int location) +ExpandAllTables(ParseState *pstate, bool logical_order, int location) { List *target = NIL; bool found_table = false; @@ -1309,6 +1329,7 @@ ExpandAllTables(ParseState *pstate, int location) nsitem, 0, true, + logical_order, location)); } @@ -1339,7 +1360,8 @@ ExpandAllTables(ParseState *pstate, int location) */ static List * ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, - bool make_target_entry, ParseExprKind exprKind) + bool make_target_entry, bool logical_order, + ParseExprKind exprKind) { Node *expr; @@ -1352,7 +1374,7 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, expr = transformExpr(pstate, (Node *) ind, exprKind); /* Expand the rowtype expression into individual fields */ - return ExpandRowReference(pstate, expr, make_target_entry); + return ExpandRowReference(pstate, expr, make_target_entry, logical_order); } /* @@ -1366,12 +1388,14 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, */ static List * ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, - int sublevels_up, int location, bool make_target_entry) + int sublevels_up, int location, bool make_target_entry, + bool logical_order) { if (make_target_entry) { /* expandNSItemAttrs handles permissions marking */ - return expandNSItemAttrs(pstate, nsitem, sublevels_up, true, location); + return expandNSItemAttrs(pstate, nsitem, sublevels_up, true, + logical_order, location); } else { @@ -1413,9 +1437,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, */ static List * ExpandRowReference(ParseState *pstate, Node *expr, - bool make_target_entry) + bool make_target_entry, bool logical_order) { List *result = NIL; + ListCell *lc; TupleDesc tupleDesc; int numAttrs; int i; @@ -1436,7 +1461,9 @@ ExpandRowReference(ParseState *pstate, Node *expr, ParseNamespaceItem *nsitem; nsitem = GetNSItemByRangeTablePosn(pstate, var->varno, var->varlevelsup); - return ExpandSingleTable(pstate, nsitem, var->varlevelsup, var->location, make_target_entry); + return ExpandSingleTable(pstate, nsitem, var->varlevelsup, + var->location, make_target_entry, + logical_order); } /* @@ -1472,6 +1499,7 @@ ExpandRowReference(ParseState *pstate, Node *expr, fselect = makeNode(FieldSelect); fselect->arg = (Expr *) copyObject(expr); fselect->fieldnum = i + 1; + fselect->fieldlognum = att->attnum; fselect->resulttype = att->atttypid; fselect->resulttypmod = att->atttypmod; /* save attribute's collation for parse_collate.c */ @@ -1482,8 +1510,12 @@ ExpandRowReference(ParseState *pstate, Node *expr, /* add TargetEntry decoration */ TargetEntry *te; + /* + * We don't assign the resno here, as we may have to sort the list + * by logical attnum first. + */ te = makeTargetEntry((Expr *) fselect, - (AttrNumber) pstate->p_next_resno++, + 0, pstrdup(NameStr(att->attname)), false); result = lappend(result, te); @@ -1492,6 +1524,24 @@ ExpandRowReference(ParseState *pstate, Node *expr, result = lappend(result, fselect); } + /* + * If caller didn't ask to make target entries, we can return the list now + */ + if (!make_target_entry) + return result; + + /* If caller asked for logical order, sort the target entries */ + if (logical_order) + list_sort(result, cmp_targetentry_logical_order); + + /* And assign the resnos now that the list is in the final order */ + foreach(lc, result) + { + TargetEntry *te = (TargetEntry *) lfirst(lc); + + te->resno = (AttrNumber) pstate->p_next_resno++; + } + return result; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 69132f729d..33e571b3b4 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1001,7 +1001,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla RelationGetRelationName(relation)); } - tupleDesc = RelationGetDescr(relation); + /* + * The expansion has to be done in the logical order (attnum) rather than + * the physical order, and the code also needs to access the constraints so + * make a full copy of the tuple desc. + */ + tupleDesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); + TupleDescSortByAttnum(tupleDesc); /* * Insert the copied attributes into the cxt for the new table definition. @@ -1110,6 +1116,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla cxt->alist = lappend(cxt->alist, stmt); } } + pfree(tupleDesc); /* * We cannot yet deal with defaults, CHECK constraints, or indexes, since diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index db843a0fbf..76c5892925 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -78,7 +78,7 @@ record_in(PG_FUNCTION_ARGS) Oid tupType = PG_GETARG_OID(1); int32 tupTypmod = PG_GETARG_INT32(2); HeapTupleHeader result; - TupleDesc tupdesc; + TupleDesc tupdesc, sorted_tupdesc; HeapTuple tuple; RecordIOData *my_extra; bool needComma = false; @@ -110,7 +110,9 @@ record_in(PG_FUNCTION_ARGS) * preserved by binary upgrades. */ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - ncolumns = tupdesc->natts; + sorted_tupdesc = CreateTupleDescCopy(tupdesc); + TupleDescSortByAttnum(sorted_tupdesc); + ncolumns = sorted_tupdesc->natts; /* * We arrange to look up the needed I/O info just once per series of @@ -161,16 +163,16 @@ record_in(PG_FUNCTION_ARGS) for (i = 0; i < ncolumns; i++) { - Form_pg_attribute att = TupleDescAttr(tupdesc, i); - ColumnIOData *column_info = &my_extra->columns[i]; + Form_pg_attribute att = TupleDescAttr(sorted_tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[att->attphysnum - 1]; Oid column_type = att->atttypid; char *column_data; /* Ignore dropped columns in datatype, but fill with nulls */ if (att->attisdropped) { - values[i] = (Datum) 0; - nulls[i] = true; + values[att->attphysnum - 1] = (Datum) 0; + nulls[att->attphysnum - 1] = true; continue; } @@ -191,7 +193,7 @@ record_in(PG_FUNCTION_ARGS) if (*ptr == ',' || *ptr == ')') { column_data = NULL; - nulls[i] = true; + nulls[att->attphysnum - 1] = true; } else { @@ -236,7 +238,7 @@ record_in(PG_FUNCTION_ARGS) } column_data = buf.data; - nulls[i] = false; + nulls[att->attphysnum - 1] = false; } /* @@ -252,10 +254,10 @@ record_in(PG_FUNCTION_ARGS) column_info->column_type = column_type; } - values[i] = InputFunctionCall(&column_info->proc, - column_data, - column_info->typioparam, - att->atttypmod); + values[att->attphysnum - 1] = InputFunctionCall(&column_info->proc, + column_data, + column_info->typioparam, + att->atttypmod); /* * Prep for next column @@ -291,6 +293,7 @@ record_in(PG_FUNCTION_ARGS) pfree(buf.data); pfree(values); pfree(nulls); + pfree(sorted_tupdesc); ReleaseTupleDesc(tupdesc); PG_RETURN_HEAPTUPLEHEADER(result); @@ -305,7 +308,7 @@ record_out(PG_FUNCTION_ARGS) HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); Oid tupType; int32 tupTypmod; - TupleDesc tupdesc; + TupleDesc tupdesc, sorted_tupdesc; HeapTupleData tuple; RecordIOData *my_extra; bool needComma = false; @@ -321,7 +324,9 @@ record_out(PG_FUNCTION_ARGS) tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - ncolumns = tupdesc->natts; + sorted_tupdesc = CreateTupleDescCopy(tupdesc); + TupleDescSortByAttnum(sorted_tupdesc); + ncolumns = sorted_tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); @@ -370,8 +375,8 @@ record_out(PG_FUNCTION_ARGS) for (i = 0; i < ncolumns; i++) { - Form_pg_attribute att = TupleDescAttr(tupdesc, i); - ColumnIOData *column_info = &my_extra->columns[i]; + Form_pg_attribute att = TupleDescAttr(sorted_tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[att->attphysnum - 1]; Oid column_type = att->atttypid; Datum attr; char *value; @@ -386,7 +391,7 @@ record_out(PG_FUNCTION_ARGS) appendStringInfoChar(&buf, ','); needComma = true; - if (nulls[i]) + if (nulls[att->attphysnum - 1]) { /* emit nothing... */ continue; @@ -405,7 +410,7 @@ record_out(PG_FUNCTION_ARGS) column_info->column_type = column_type; } - attr = values[i]; + attr = values[att->attphysnum - 1]; value = OutputFunctionCall(&column_info->proc, attr); /* Detect whether we need double quotes for this value */ @@ -442,6 +447,7 @@ record_out(PG_FUNCTION_ARGS) pfree(values); pfree(nulls); + pfree(sorted_tupdesc); ReleaseTupleDesc(tupdesc); PG_RETURN_CSTRING(buf.data); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 837dc6b20d..fcd7146d36 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -261,6 +261,10 @@ typedef struct int num_new_cols; /* length of new_colnames[] array */ char **new_colnames; /* array of C strings */ bool *is_new_col; /* array of bool flags */ + AttrNumber *mappings; /* attsnum / offset in new_colnames mappings, + can be NULL. attsnum are 0-based, and offset + 1-based (to rely on AttributeNumberIsValid). + Array size if num_cols. */ /* This flag tells whether we should actually print a column alias list */ bool printaliases; @@ -4320,6 +4324,7 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, { int ncolumns; char **real_colnames; + AttrNumber *mappings; bool changed_any; int noldcolumns; int i; @@ -4341,15 +4346,23 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, ncolumns = tupdesc->natts; real_colnames = (char **) palloc(ncolumns * sizeof(char *)); + mappings = (AttrNumber *) palloc(ncolumns * sizeof(AttrNumber)); + j = 1; for (i = 0; i < ncolumns; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, i); if (attr->attisdropped) + { real_colnames[i] = NULL; + mappings[attr->attnum - 1] = InvalidAttrNumber; + } else + { real_colnames[i] = pstrdup(NameStr(attr->attname)); + mappings[attr->attnum - 1] = j++; + } } relation_close(rel, AccessShareLock); } @@ -4360,8 +4373,10 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, ncolumns = list_length(rte->eref->colnames); real_colnames = (char **) palloc(ncolumns * sizeof(char *)); + mappings = (AttrNumber *) palloc(ncolumns * sizeof(AttrNumber)); i = 0; + j = 1; foreach(lc, rte->eref->colnames) { /* @@ -4370,9 +4385,24 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, * treat it as dropped. */ char *cname = strVal(lfirst(lc)); + AttrNumber mapping; + + /* + * Rte contains 1-based positions (0 is !AttributeNumberIsValid), + * so shift it to map a position in the colname array + */ + if (rte->mappings != NULL) + mapping = rte->mappings[i] - 1; + else + mapping = i; if (cname[0] == '\0') + { cname = NULL; + mappings[mapping] = InvalidAttrNumber; + } + else + mappings[mapping] = j++; real_colnames[i] = cname; i++; } @@ -4398,6 +4428,7 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, */ colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *)); colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool)); + colinfo->mappings = mappings; /* * Scan the columns, select a unique alias for each one, and store it in @@ -4589,6 +4620,7 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, colinfo->num_new_cols = nnewcolumns; colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *)); colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool)); + colinfo->mappings = NULL; /* * Generating the new_colnames array is a bit tricky since any new columns @@ -4647,7 +4679,8 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, colinfo->colnames[i] == NULL) i++; Assert(i < colinfo->num_cols); - Assert(ic == colinfo->leftattnos[i]); + //FIXME not guaranteed anymore if logical order is different + //Assert(ic == colinfo->leftattnos[i]); /* Use the already-assigned name of this column */ colinfo->new_colnames[j] = colinfo->colnames[i]; i++; @@ -4696,7 +4729,8 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, colinfo->colnames[i] == NULL) i++; Assert(i < colinfo->num_cols); - Assert(ic == colinfo->rightattnos[i]); + //FIXME not guaranteed anymore if logical order is different + //Assert(ic == colinfo->rightattnos[i]); /* Use the already-assigned name of this column */ colinfo->new_colnames[j] = colinfo->colnames[i]; i++; @@ -11889,16 +11923,45 @@ static void get_column_alias_list(deparse_columns *colinfo, deparse_context *context) { StringInfo buf = context->buf; - int i; + int i, max; bool first = true; /* Don't print aliases if not needed */ if (!colinfo->printaliases) return; - for (i = 0; i < colinfo->num_new_cols; i++) + /* + * Use mappings is those were computed, which length is num_cols, otherwise + * loop on the new_colnames, size num_new_cols. + */ + if (colinfo->mappings) + max = colinfo->num_cols; + else + max = colinfo->num_new_cols; + + for (i = 0; i < max; i++) { - char *colname = colinfo->new_colnames[i]; + char *colname; + AttrNumber mapping; + + /* + * If no mapping simply use the current loop counter, otherwise fetch + * the correct mapping in new_colnames. + */ + if (!colinfo->mappings) + { + mapping = i; + } + else + { + /* Skip dropped attributes */ + if (!AttributeNumberIsValid(colinfo->mappings[i])) + continue; + mapping = colinfo->mappings[i] - 1; + } + + colname = colinfo->new_colnames[mapping]; + Assert(colname[0] != '\0'); if (first) { diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 8a98060956..febc305244 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -576,8 +576,11 @@ RelationBuildTupleDesc(Relation relation) attphysnum = attp->attphysnum; if (attphysnum <= 0 || attphysnum > RelationGetNumberOfAttributes(relation)) - elog(ERROR, "invalid attribute number %d for relation \"%s\"", + elog(ERROR, "invalid physical attribute number %d for relation \"%s\"", attp->attphysnum, RelationGetRelationName(relation)); + if (attp->attnum <= 0 || attp->attnum > RelationGetNumberOfAttributes(relation)) + elog(ERROR, "invalid logical attribute number %d for relation \"%s\"", + attp->attnum, RelationGetRelationName(relation)); memcpy(TupleDescAttr(relation->rd_att, attphysnum - 1), attp, diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index a25ec298fd..0e56b546e3 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1534,6 +1534,7 @@ describeOneTableDetails(const char *schemaname, char relpersistence; char relreplident; char *relam; + bool haslogicalorder; } tableinfo; bool show_column_details = false; @@ -1546,7 +1547,28 @@ describeOneTableDetails(const char *schemaname, initPQExpBuffer(&tmpbuf); /* Get general table info */ - if (pset.sversion >= 120000) + if (pset.sversion >= 150000) /* FXME - bump me when pg15 branched */ + { + printfPQExpBuffer(&buf, + "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " + "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, " + "false AS relhasoids, c.relispartition, %s, c.reltablespace, " + "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " + "c.relpersistence, c.relreplident, am.amname,\n" + " (SELECT count(*) > 0 FROM pg_catalog.pg_attribute a\n" + " WHERE a.attrelid = c.oid\n" + " AND a.attphysnum != a.attnum)\n" + "FROM pg_catalog.pg_class c\n " + "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" + "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n" + "WHERE c.oid = '%s';", + (verbose ? + "pg_catalog.array_to_string(c.reloptions || " + "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n" + : "''"), + oid); + } + else if (pset.sversion >= 120000) { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " @@ -1666,6 +1688,11 @@ describeOneTableDetails(const char *schemaname, (char *) NULL : pg_strdup(PQgetvalue(res, 0, 14)); else tableinfo.relam = NULL; + /* FIXME - bump me when pg15 branched */ + if (pset.sversion >= 150000) + tableinfo.haslogicalorder = strcmp(PQgetvalue(res, 0, 15), "t") == 0; + else + tableinfo.haslogicalorder = false; PQclear(res); res = NULL; @@ -1955,16 +1982,8 @@ describeOneTableDetails(const char *schemaname, } appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_attribute a"); - if (pset.sversion >= 150000) /* FIXME - bump me when pg15 branched */ - { - appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attphysnum > 0 AND NOT a.attisdropped", oid); - appendPQExpBufferStr(&buf, "\nORDER BY a.attphysnum;"); - } - else - { - appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid); - appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;"); - } + appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid); + appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;"); res = PSQLexec(buf.data); if (!res) @@ -2386,6 +2405,36 @@ describeOneTableDetails(const char *schemaname, PGresult *result = NULL; int tuples = 0; + /* + * print physical attribute order if different from logical + * FIXME - bump me when pg15 branched + */ + if (pset.sversion >= 150000 && tableinfo.haslogicalorder && verbose) + { + Assert(tableinfo.relkind == RELKIND_RELATION); + + printfPQExpBuffer(&buf, + "SELECT string_agg(a.attname, ', ')\n" + "FROM pg_catalog.pg_class c\n" + "JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid\n" + "WHERE c.oid = '%s' \n" + "AND a.attphysnum > 0 \n" + "AND NOT a.attisdropped", oid); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + else + tuples = PQntuples(result); + + Assert(tuples == 1); + + printfPQExpBuffer(&buf, "(%s)", PQgetvalue(result, 0, 0)); + PQclear(result); + + printTableAddFooter(&cont, _("Physical order:")); + printTableAddFooter(&cont, buf.data); + } + /* print indexes */ if (tableinfo.hasindex) { diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 28dd6de18b..f519758b22 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -147,6 +147,8 @@ extern void TupleDescInitEntryCollation(TupleDesc desc, AttrNumber attributeNumber, Oid collationid); +extern void TupleDescSortByAttnum(TupleDesc desc); + extern TupleDesc BuildDescForRelation(List *schema); extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 746566eb8d..d2e8b5f765 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -82,6 +82,11 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75, */ int16 attphysnum; + /* FIXME Same as attphysnum, but for the "logical" order, e.g. for a + * star-expansion. + */ + int16 attnum; + /* * attndims is the declared number of dimensions, if an array type, * otherwise zero. @@ -207,6 +212,7 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75, typedef FormData_pg_attribute *Form_pg_attribute; DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnam_index, 2658, AttributeRelidNameIndexId, on pg_attribute using btree(attrelid oid_ops, attname name_ops)); +DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 8294, AttributeReliNumIndexId, on pg_attribute using btree(attrelid oid_ops, attnum int2_ops)); DECLARE_UNIQUE_INDEX_PKEY(pg_attribute_relid_attphysnum_index, 2659, AttributeRelidPhysNumIndexId, on pg_attribute using btree(attrelid oid_ops, attphysnum int2_ops)); #ifdef EXPOSE_TO_CLIENT_CODE diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 73f635b455..2e6c355db2 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1109,6 +1109,8 @@ typedef struct RangeTblEntry */ List *functions; /* list of RangeTblFunction nodes */ bool funcordinality; /* is this called WITH ORDINALITY? */ + int nummappings; + AttrNumber *mappings; /* attphysnum / attnum mappings */ /* * Fields valid for a TableFunc RTE (else NULL): diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 51505eee85..7dad6f4fb6 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -199,6 +199,7 @@ typedef struct Var * table, or INNER_VAR/OUTER_VAR/etc */ AttrNumber varattno; /* attribute number of this var, or zero for * all attrs ("whole-row Var") */ + AttrNumber varnum; /* logical attribute number of this var FIXME */ Oid vartype; /* pg_type OID for the type of this var */ int32 vartypmod; /* pg_attribute typmod value */ Oid varcollid; /* OID of collation, or InvalidOid if none */ @@ -815,7 +816,8 @@ typedef struct FieldSelect { Expr xpr; Expr *arg; /* input expression */ - AttrNumber fieldnum; /* attribute number of field to extract */ + AttrNumber fieldnum; /* physical attribute number of field to extract */ + AttrNumber fieldlognum; /* logical attribute number of field (can be 0) */ Oid resulttype; /* type of the field (result type of this * node) */ int32 resulttypmod; /* output typmod (usually -1) */ diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h index 04668ba1c0..5e4fb21bc8 100644 --- a/src/include/optimizer/tlist.h +++ b/src/include/optimizer/tlist.h @@ -48,6 +48,7 @@ extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target); extern void split_pathtarget_at_srfs(PlannerInfo *root, PathTarget *target, PathTarget *input_target, List **targets, List **targets_contain_srfs); +extern int cmp_targetentry_logical_order(const ListCell *a, const ListCell *b); /* Convenience macro to get a PathTarget with valid cost/width fields */ #define create_pathtarget(root, tlist) \ diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index cf9c759025..1ab65991ca 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -270,6 +270,8 @@ struct ParseNamespaceItem int p_rtindex; /* The relation's index in the rangetable */ /* array of same length as p_names->colnames: */ ParseNamespaceColumn *p_nscolumns; /* per-column data */ + /* array of length p_names->colnames + 1: */ + AttrNumber *p_mappings; /* 1-based attnum -> attphysnum mappings */ bool p_rel_visible; /* Relation name is visible? */ bool p_cols_visible; /* Column names visible as unqualified refs? */ bool p_lateral_only; /* Is only visible to LATERAL expressions? */ @@ -300,7 +302,8 @@ struct ParseNamespaceItem struct ParseNamespaceColumn { Index p_varno; /* rangetable index */ - AttrNumber p_varattno; /* attribute number of the column */ + AttrNumber p_varattno; /* physical attribute number of the column */ + AttrNumber p_varnum; /* logical attribute number of the column */ Oid p_vartype; /* pg_type OID */ int32 p_vartypmod; /* type modifier value */ Oid p_varcollid; /* OID of collation, or InvalidOid */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index de21c3c649..97e1c3f453 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -114,7 +114,7 @@ extern List *expandNSItemVars(ParseNamespaceItem *nsitem, List **colnames); extern List *expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, int sublevels_up, bool require_col_privs, - int location); + bool logical_order, int location); extern int attnameAttNum(Relation rd, const char *attname, bool sysColOK); extern const NameData *attnumAttName(Relation rd, int attid); extern Oid attnumTypeId(Relation rd, int attid); diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index f34c8474b4..fbd079f8b2 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -414,9 +414,22 @@ ttdummy(PG_FUNCTION_ARGS) query = (char *) palloc(100 + 16 * natts); /* - * Construct query: INSERT INTO _relation_ VALUES ($1, ...) + * Construct query: + * INSERT INTO _relation_ (field1, ...) VALUES ($1, ...) + * We have to specify the fields explicitly as we provide the value in + * the relation physical order and not the possibly different logical + * order. */ - sprintf(query, "INSERT INTO %s VALUES (", relname); + sprintf(query, "INSERT INTO %s(", relname); + for (i = 0; i < natts; i++) + { + if (i > 0) + sprintf(query + strlen(query), ", "); + + sprintf(query + strlen(query), + "%s", NameStr(TupleDescAttr(tupdesc, i)->attname)); + } + sprintf(query + strlen(query), ") VALUES ("); for (i = 1; i <= natts; i++) { sprintf(query + strlen(query), "$%d%s", -- 2.33.1