From 0ee26100b64702f2fb4cd65c6dfdfb8f31e88130 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 10 Aug 2006 02:36:29 +0000 Subject: [PATCH] Fix UNION/INTERSECT/EXCEPT so that when two inputs being merged have same data type and same typmod, we show that typmod as the output typmod, rather than generic -1. This responds to several complaints over the past few years about UNIONs unexpectedly dropping length or precision info. --- src/backend/nodes/copyfuncs.c | 3 +- src/backend/nodes/equalfuncs.c | 3 +- src/backend/nodes/outfuncs.c | 3 +- src/backend/nodes/readfuncs.c | 3 +- src/backend/optimizer/path/allpaths.c | 6 +- src/backend/optimizer/prep/prepjointree.c | 3 +- src/backend/optimizer/prep/prepunion.c | 6 +- src/backend/optimizer/util/tlist.c | 4 +- src/backend/parser/analyze.c | 74 ++++++++++++++++------- src/include/catalog/catversion.h | 4 +- src/include/nodes/parsenodes.h | 5 +- 11 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8903d6b42d..77ccd64a7d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.345 2006/08/02 01:59:45 joe Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.346 2006/08/10 02:36:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1793,6 +1793,7 @@ _copySetOperationStmt(SetOperationStmt *from) COPY_NODE_FIELD(larg); COPY_NODE_FIELD(rarg); COPY_NODE_FIELD(colTypes); + COPY_NODE_FIELD(colTypmods); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index d49e02b3d8..4b749e0fc6 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.279 2006/08/02 01:59:45 joe Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.280 2006/08/10 02:36:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -743,6 +743,7 @@ _equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b) COMPARE_NODE_FIELD(larg); COMPARE_NODE_FIELD(rarg); COMPARE_NODE_FIELD(colTypes); + COMPARE_NODE_FIELD(colTypmods); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 34555e1c56..39ac8e4c62 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.280 2006/08/02 01:59:45 joe Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.281 2006/08/10 02:36:28 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1574,6 +1574,7 @@ _outSetOperationStmt(StringInfo str, SetOperationStmt *node) WRITE_NODE_FIELD(larg); WRITE_NODE_FIELD(rarg); WRITE_NODE_FIELD(colTypes); + WRITE_NODE_FIELD(colTypmods); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 7459acb30c..80fa88e0da 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.193 2006/08/02 01:59:45 joe Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.194 2006/08/10 02:36:28 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -245,6 +245,7 @@ _readSetOperationStmt(void) READ_NODE_FIELD(larg); READ_NODE_FIELD(rarg); READ_NODE_FIELD(colTypes); + READ_NODE_FIELD(colTypmods); READ_DONE(); } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 1d7e20c1e8..fc618f72d1 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.150 2006/08/02 01:59:45 joe Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.151 2006/08/10 02:36:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -813,6 +813,10 @@ recurse_pushdown_safe(Node *setOp, Query *topquery, * Compare tlist's datatypes against the list of set-operation result types. * For any items that are different, mark the appropriate element of * differentTypes[] to show that this column will have type conversions. + * + * We don't have to care about typmods here: the only allowed difference + * between set-op input and output typmods is input is a specific typmod + * and output is -1, and that does not require a coercion. */ static void compare_tlist_datatypes(List *tlist, List *colTypes, diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 856181d090..2fe7847304 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.39 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.40 2006/08/10 02:36:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -716,6 +716,7 @@ is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes) Assert(subquery != NULL); /* Leaf nodes are OK if they match the toplevel column types */ + /* We don't have to compare typmods here */ return tlist_same_datatypes(subquery->targetList, colTypes, true); } else if (IsA(setOp, SetOperationStmt)) diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 3b9e740cad..3bf7223199 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -22,7 +22,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.132 2006/04/30 18:30:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.133 2006/08/10 02:36:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -152,6 +152,10 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction, * flag: if >= 0, add a resjunk output column indicating value of flag * refnames_tlist: targetlist to take column names from * *sortClauses: receives list of SortClauses for result plan, if any + * + * We don't have to care about typmods here: the only allowed difference + * between set-op input and output typmods is input is a specific typmod + * and output is -1, and that does not require a coercion. */ static Plan * recurse_set_operations(Node *setOp, PlannerInfo *root, diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index c75c496065..74ebb3fc24 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.72 2006/03/05 15:58:32 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.73 2006/08/10 02:36:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -174,6 +174,8 @@ get_sortgrouplist_exprs(List *sortClauses, List *targetList) * * Resjunk columns are ignored if junkOK is true; otherwise presence of * a resjunk column will always cause a 'false' result. + * + * Note: currently no callers care about comparing typmods. */ bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 4f00d98b9b..6921e1d77c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.343 2006/08/02 14:14:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.344 2006/08/10 02:36:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -131,7 +131,8 @@ static void transformFKConstraints(ParseState *pstate, bool skipValidation, bool isAddConstraint); static void applyColumnNames(List *dst, List *src); -static List *getSetColTypes(ParseState *pstate, Node *node); +static void getSetColTypes(ParseState *pstate, Node *node, + List **colTypes, List **colTypmods); static void transformLockingClause(Query *qry, LockingClause *lc); static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); @@ -2312,7 +2313,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) List *lockingClause; Node *node; ListCell *left_tlist, - *dtlist, + *lct, + *lcm, *l; List *targetvars, *targetnames, @@ -2395,9 +2397,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) targetnames = NIL; left_tlist = list_head(leftmostQuery->targetList); - foreach(dtlist, sostmt->colTypes) + forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods) { - Oid colType = lfirst_oid(dtlist); + Oid colType = lfirst_oid(lct); + int32 colTypmod = lfirst_int(lcm); TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist); char *colName; TargetEntry *tle; @@ -2408,7 +2411,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) expr = (Expr *) makeVar(leftmostRTI, lefttle->resno, colType, - -1, + colTypmod, 0); tle = makeTargetEntry(expr, (AttrNumber) pstate->p_next_resno++, @@ -2609,8 +2612,12 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) SetOperationStmt *op = makeNode(SetOperationStmt); List *lcoltypes; List *rcoltypes; - ListCell *l; - ListCell *r; + List *lcoltypmods; + List *rcoltypmods; + ListCell *lct; + ListCell *rct; + ListCell *lcm; + ListCell *rcm; const char *context; context = (stmt->op == SETOP_UNION ? "UNION" : @@ -2630,24 +2637,43 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) * Verify that the two children have the same number of non-junk * columns, and determine the types of the merged output columns. */ - lcoltypes = getSetColTypes(pstate, op->larg); - rcoltypes = getSetColTypes(pstate, op->rarg); + getSetColTypes(pstate, op->larg, &lcoltypes, &lcoltypmods); + getSetColTypes(pstate, op->rarg, &rcoltypes, &rcoltypmods); if (list_length(lcoltypes) != list_length(rcoltypes)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("each %s query must have the same number of columns", context))); + Assert(list_length(lcoltypes) == list_length(lcoltypmods)); + Assert(list_length(rcoltypes) == list_length(rcoltypmods)); op->colTypes = NIL; - forboth(l, lcoltypes, r, rcoltypes) + op->colTypmods = NIL; + /* don't have a "foreach4", so chase two of the lists by hand */ + lcm = list_head(lcoltypmods); + rcm = list_head(rcoltypmods); + forboth(lct, lcoltypes, rct, rcoltypes) { - Oid lcoltype = lfirst_oid(l); - Oid rcoltype = lfirst_oid(r); + Oid lcoltype = lfirst_oid(lct); + Oid rcoltype = lfirst_oid(rct); + int32 lcoltypmod = lfirst_int(lcm); + int32 rcoltypmod = lfirst_int(rcm); Oid rescoltype; + int32 rescoltypmod; + /* select common type, same as CASE et al */ rescoltype = select_common_type(list_make2_oid(lcoltype, rcoltype), context); + /* if same type and same typmod, use typmod; else default */ + if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) + rescoltypmod = lcoltypmod; + else + rescoltypmod = -1; op->colTypes = lappend_oid(op->colTypes, rescoltype); + op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); + + lcm = lnext(lcm); + rcm = lnext(rcm); } return (Node *) op; @@ -2656,17 +2682,19 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) /* * getSetColTypes - * Get output column types of an (already transformed) set-op node + * Get output column types/typmods of an (already transformed) set-op node */ -static List * -getSetColTypes(ParseState *pstate, Node *node) +static void +getSetColTypes(ParseState *pstate, Node *node, + List **colTypes, List **colTypmods) { + *colTypes = NIL; + *colTypmods = NIL; if (IsA(node, RangeTblRef)) { RangeTblRef *rtr = (RangeTblRef *) node; RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable); Query *selectQuery = rte->subquery; - List *result = NIL; ListCell *tl; Assert(selectQuery != NULL); @@ -2677,9 +2705,11 @@ getSetColTypes(ParseState *pstate, Node *node) if (tle->resjunk) continue; - result = lappend_oid(result, exprType((Node *) tle->expr)); + *colTypes = lappend_oid(*colTypes, + exprType((Node *) tle->expr)); + *colTypmods = lappend_int(*colTypmods, + exprTypmod((Node *) tle->expr)); } - return result; } else if (IsA(node, SetOperationStmt)) { @@ -2687,13 +2717,11 @@ getSetColTypes(ParseState *pstate, Node *node) /* Result already computed during transformation of node */ Assert(op->colTypes != NIL); - return op->colTypes; + *colTypes = op->colTypes; + *colTypmods = op->colTypmods; } else - { elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); - return NIL; /* keep compiler quiet */ - } } /* Attach column names from a ColumnDef list to a TargetEntry list */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index dbf0395905..b97c3d07ec 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.347 2006/08/06 03:53:44 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.348 2006/08/10 02:36:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200608051 +#define CATALOG_VERSION_NO 200608091 #endif diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d0fa16ff51..e2567ff8e4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.320 2006/08/02 01:59:47 joe Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.321 2006/08/10 02:36:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -769,7 +769,8 @@ typedef struct SetOperationStmt /* Eventually add fields for CORRESPONDING spec here */ /* Fields derived during parse analysis: */ - List *colTypes; /* list of OIDs of output column types */ + List *colTypes; /* OID list of output column type OIDs */ + List *colTypmods; /* integer list of output column typmods */ } SetOperationStmt;