Hi 2017-03-14 16:33 GMT+01:00 Surafel Temesgen <surafel3...@gmail.com>:
> > hi > > Some errors are related to just CORRESPONDING without any columns. So >> using expr doesn't help here. So parse node CORRESPONDING can solve both >> issues. >> > In current implementation pointing to a node means pointing to a node’s > first element so I don’t think we can be able to point to CORRESPONDING > without any columns > > I find out that there is already a node prepare for the case called > A_Const. > The attached patch use that node > It looks better I fixed format of comments and some too long lines. all regress tests passed I have not any objection - I'll mark this patch as ready for commiter Regards Pavel > > Regards > Surafel >
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml index 30792f45f1..c3cdee54ad 100644 --- a/doc/src/sgml/queries.sgml +++ b/doc/src/sgml/queries.sgml @@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab <primary>EXCEPT</primary> </indexterm> <indexterm zone="queries-union"> + <primary>CORRESPONDING</primary> + </indexterm> + <indexterm zone="queries-union"> <primary>set union</primary> </indexterm> <indexterm zone="queries-union"> @@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab The results of two queries can be combined using the set operations union, intersection, and difference. The syntax is <synopsis> -<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable> -<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable> -<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable> +<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable> +<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable> +<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable> </synopsis> <replaceable>query1</replaceable> and <replaceable>query2</replaceable> are queries that can use any of @@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab </para> <para> - In order to calculate the union, intersection, or difference of two - queries, the two queries must be <quote>union compatible</quote>, - which means that they return the same number of columns and - the corresponding columns have compatible data types, as - described in <xref linkend="typeconv-union-case">. + <literal>EXCEPT</> returns all rows that are in the result of + <replaceable>query1</replaceable> but not in the result of + <replaceable>query2</replaceable>. (This is sometimes called the + <firstterm>difference</> between two queries.) Again, duplicates + are eliminated unless <literal>EXCEPT ALL</> is used. + </para> + + <para> + <literal>CORRESPONDING</> returns all columns that are in both + <replaceable>query1</> and <replaceable>query2</> with the same name. + </para> + + <para> + <literal>CORRESPONDING BY</> returns all columns in the column list + that are also in both <replaceable>query1</> and + <replaceable>query2</> with the same name. </para> </sect1> diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml index 57396d7c24..f98c22e696 100644 --- a/doc/src/sgml/sql.sgml +++ b/doc/src/sgml/sql.sgml @@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac [ WHERE <replaceable class="PARAMETER">condition</replaceable> ] [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ] [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ] - [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ] + [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ] [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ] [ OFFSET <replaceable class="PARAMETER">start</replaceable> ] diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 25fd051d6e..5dacb33e9e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2977,6 +2977,7 @@ _copySelectStmt(const SelectStmt *from) COPY_NODE_FIELD(withClause); COPY_SCALAR_FIELD(op); COPY_SCALAR_FIELD(all); + COPY_NODE_FIELD(correspondingClause); COPY_NODE_FIELD(larg); COPY_NODE_FIELD(rarg); @@ -2992,6 +2993,7 @@ _copySetOperationStmt(const SetOperationStmt *from) COPY_SCALAR_FIELD(all); COPY_NODE_FIELD(larg); COPY_NODE_FIELD(rarg); + COPY_NODE_FIELD(correspondingColumns); COPY_NODE_FIELD(colTypes); COPY_NODE_FIELD(colTypmods); COPY_NODE_FIELD(colCollations); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 67529e3f86..4d66ca309a 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1041,6 +1041,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b) COMPARE_NODE_FIELD(withClause); COMPARE_SCALAR_FIELD(op); COMPARE_SCALAR_FIELD(all); + COMPARE_NODE_FIELD(correspondingClause); COMPARE_NODE_FIELD(larg); COMPARE_NODE_FIELD(rarg); @@ -1054,6 +1055,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b) COMPARE_SCALAR_FIELD(all); COMPARE_NODE_FIELD(larg); COMPARE_NODE_FIELD(rarg); + COMPARE_NODE_FIELD(correspondingColumns); COMPARE_NODE_FIELD(colTypes); COMPARE_NODE_FIELD(colTypmods); COMPARE_NODE_FIELD(colCollations); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 6e52eb7231..7102ea96c2 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->lockingClause, context)) return true; + if (walker(stmt->correspondingClause, context)) + return true; if (walker(stmt->withClause, context)) return true; if (walker(stmt->larg, context)) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 7418fbeded..74e0a8f257 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2599,6 +2599,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node) WRITE_NODE_FIELD(withClause); WRITE_ENUM_FIELD(op, SetOperation); WRITE_BOOL_FIELD(all); + WRITE_NODE_FIELD(correspondingClause); WRITE_NODE_FIELD(larg); WRITE_NODE_FIELD(rarg); } @@ -2906,6 +2907,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node) WRITE_BOOL_FIELD(all); WRITE_NODE_FIELD(larg); WRITE_NODE_FIELD(rarg); + WRITE_NODE_FIELD(correspondingColumns); WRITE_NODE_FIELD(colTypes); WRITE_NODE_FIELD(colTypmods); WRITE_NODE_FIELD(colCollations); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index d3bbc02f24..44a8ecb6a5 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -416,6 +416,7 @@ _readSetOperationStmt(void) READ_BOOL_FIELD(all); READ_NODE_FIELD(larg); READ_NODE_FIELD(rarg); + READ_NODE_FIELD(correspondingColumns); READ_NODE_FIELD(colTypes); READ_NODE_FIELD(colTypmods); READ_NODE_FIELD(colCollations); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 1389db18ba..a06451f9fd 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations, Index varno, bool hack_constants, List *input_tlist, - List *refnames_tlist); + List *refnames_tlist, + bool no_corresponding); static List *generate_append_tlist(List *colTypes, List *colCollations, bool flag, List *input_tlists, @@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node, static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); +static List *make_corresponding_target(List *corresponding_list, List *subroot_list); /* @@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root) leftmostQuery->targetList, &top_tlist); } + /* + * If corresponding column specified, we take column names from it. + */ + else if (topop->correspondingColumns != NIL ) + { + /* + * Recurse on setOperations tree to generate paths for set ops. The + * final output path should have just the column types shown as the + * output from the top-level node, plus possibly resjunk working + * columns (we can rely on upper-level nodes to deal with that). + */ + path = recurse_set_operations((Node *) topop, root, + topop->colTypes, topop->colCollations, + true, -1, + topop->correspondingColumns, + &top_tlist, + NULL); + } else { /* @@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, List **pTargetList, double *pNumGroups) { + SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations; + if (IsA(setOp, RangeTblRef)) { RangeTblRef *rtr = (RangeTblRef *) setOp; @@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, path = (Path *) create_subqueryscan_path(root, rel, subpath, NIL, NULL); - /* - * Figure out the appropriate target list, and update the - * SubqueryScanPath with the PathTarget form of that. - */ - tlist = generate_setop_tlist(colTypes, colCollations, + if (topop->correspondingColumns != NIL ) + { + List *correspondingTarget; + + /* + * make target list that only contains corresponding column + * from sub-queries list ito use it for projection + */ + correspondingTarget = make_corresponding_target( + topop->correspondingColumns, + subroot->processed_tlist); + + /* + * Figure out the appropriate target list, and update the + * SubqueryScanPath with the PathTarget form of that. + */ + tlist = generate_setop_tlist(colTypes, colCollations, flag, + rtr->rtindex, true, + correspondingTarget, + refnames_tlist, false); + + path = apply_projection_to_path(root, rel, path, + create_pathtarget(root, tlist)); + + /* Return the fully-fledged tlist to caller, too */ + *pTargetList = tlist; + + } + else + { + /* + * Figure out the appropriate target list, and update the + * SubqueryScanPath with the PathTarget form of that. + */ + tlist = generate_setop_tlist(colTypes, colCollations, flag, rtr->rtindex, true, subroot->processed_tlist, - refnames_tlist); + refnames_tlist, true); - path = apply_projection_to_path(root, rel, path, + path = apply_projection_to_path(root, rel, path, create_pathtarget(root, tlist)); - /* Return the fully-fledged tlist to caller, too */ - *pTargetList = tlist; - + /* Return the fully-fledged tlist to caller, too */ + *pTargetList = tlist; + } /* * Estimate number of groups if caller wants it. If the subquery used * grouping or aggregation, its output is probably mostly unique @@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, 0, false, *pTargetList, - refnames_tlist); + refnames_tlist, true); path = apply_projection_to_path(root, path->parent, path, @@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations, Index varno, bool hack_constants, List *input_tlist, - List *refnames_tlist) + List *refnames_tlist, + bool no_corresponding) { List *tlist = NIL; int resno = 1; @@ -1025,9 +1078,11 @@ generate_setop_tlist(List *colTypes, List *colCollations, TargetEntry *reftle = (TargetEntry *) lfirst(rtlc); rtlc = lnext(rtlc); - - Assert(inputtle->resno == resno); - Assert(reftle->resno == resno); + if (no_corresponding) + { + Assert(inputtle->resno == resno); + Assert(reftle->resno == resno); + } Assert(!inputtle->resjunk); Assert(!reftle->resjunk); @@ -2111,3 +2166,72 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, /* Now translate for this child */ return adjust_appendrel_attrs(root, node, appinfo); } + +/* + * generate target list from left target list with the order + * of right target list + */ +static List * +make_corresponding_target(List *corresponding_list, List *subroot_list) +{ + Index internal = 0; + ListCell *ltl; + ListCell *rtl; + int size; + int i; + List *matchingColumns = NIL; + TargetEntry *simple_te_array; + + size = list_length(corresponding_list) + 1; + + /* Use array to find the order of corresponding columen */ + simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry)); + foreach(ltl, corresponding_list) + { + foreach(rtl, subroot_list) + { + TargetEntry *ltle = (TargetEntry *) lfirst(ltl); + TargetEntry *rtle = (TargetEntry *) lfirst(rtl); + + elog(DEBUG4, "%s", ltle->resname); + + /* Names of the columns must be resolved before calling this method. */ + Assert(ltle->resname != NULL); + Assert(rtle->resname != NULL); + + /* If column names are the same, add it to array. */ + if (strcmp(ltle->resname, rtle->resname) == 0) + { + simple_te_array[internal].xpr = rtle->xpr; + simple_te_array[internal].expr = rtle->expr; + simple_te_array[internal].resno = rtle->resno; + simple_te_array[internal].resname = rtle->resname; + simple_te_array[internal].ressortgroupref = + rtle->ressortgroupref; + simple_te_array[internal].resorigtbl = rtle->resorigtbl; + simple_te_array[internal].resorigcol = rtle->resorigcol; + simple_te_array[internal].resjunk = rtle->resjunk; + internal++; + continue; + } + } + } + /* traverse the array and make targetlist */ + for (i = 0; i < internal; i++) + { + TargetEntry *tle = makeNode(TargetEntry); + + tle->xpr = simple_te_array[i].xpr; + tle->expr = simple_te_array[i].expr; + tle->resno = simple_te_array[i].resno; + tle->resname = simple_te_array[i].resname; + tle->ressortgroupref = simple_te_array[i].ressortgroupref; + tle->resorigtbl = simple_te_array[i].resorigtbl; + tle->resorigcol = simple_te_array[i].resorigcol; + tle->resjunk = simple_te_array[i].resjunk; + + matchingColumns = lappend(matchingColumns, tle); + + } + return matchingColumns; +} diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 3571e50aea..e26ce46f3b 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt); static void transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, bool pushedDown); +static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist); +static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist, + SetOperationStmt *op, List **targetlist, ParseState *parentParseState, + const char *context); #ifdef RAW_EXPRESSION_COVERAGE_TEST static bool test_raw_expression_coverage(Node *node, void *context); #endif @@ -1661,7 +1665,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) qry->targetList = NIL; targetvars = NIL; targetnames = NIL; - left_tlist = list_head(leftmostQuery->targetList); + /* for corresponding clause limits top-level query targetlist to those + * corresponding column list only + */ + if (sostmt->correspondingColumns != NIL ) + left_tlist = list_head(sostmt->correspondingColumns); + else + left_tlist = list_head(leftmostQuery->targetList); forthree(lct, sostmt->colTypes, lcm, sostmt->colTypmods, @@ -1921,8 +1931,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, SetOperationStmt *op = makeNode(SetOperationStmt); List *ltargetlist; List *rtargetlist; - ListCell *ltl; - ListCell *rtl; const char *context; context = (stmt->op == SETOP_UNION ? "UNION" : @@ -1933,6 +1941,215 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, op->all = stmt->all; /* + * If CORRESPONDING is specified, syntax and column name validities checked, + * column filtering is done by a subquery later on. + */ + if (stmt->correspondingClause == NIL ) + { + /* No CORRESPONDING clause, no operation needed for column filtering */ + op->correspondingColumns = stmt->correspondingClause; + } + else if (linitial(stmt->correspondingClause) == NULL ) + { + /* + * CORRESPONDING clause, find matching column names from both tables. + * If there are none then it is a syntax error. + */ + Query *largQuery; + Query *rargQuery; + List *matchingColumns; + List *rightCorrespondingColumns; + + /* Analyze left query to resolve column names. */ + largQuery = parse_sub_analyze((Node *) stmt->larg, + pstate, NULL, false, false ); + + /* Analyze right query to resolve column names. */ + rargQuery = parse_sub_analyze((Node *) stmt->rarg, + pstate, NULL, false, false ); + + /* Find matching columns from both queries. */ + matchingColumns = determineMatchingColumns(largQuery->targetList, + rargQuery->targetList); + + /* there may be out-of-order resnos in corresponding target list */ + op->correspondingColumns = orderCorrespondingList(matchingColumns); + + /* + * If matchingColumns is empty, there is an error. + * At least one column in the select lists must have the same name. + */ + if (list_length(matchingColumns) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("there are not any corresponding name"), + errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name", + context), + parser_errposition(pstate, + exprLocation((Node *) + linitial(largQuery->targetList))))); + } + + /* + * matchingColumns contain target list as it appear in left query + * targetList we need matching column as it appear in right query + * targetlist inorder to make output column type for corresponding + * columns + */ + rightCorrespondingColumns = determineMatchingColumns( + rargQuery->targetList, + largQuery->targetList); + + /* make union'd datatype of output column */ + makeUnionDatatype(matchingColumns, rightCorrespondingColumns, + op, targetlist, pstate, context); + } + else + { + /* + * CORRESPONDING BY clause, find matching column names from both tables + * and intersect them with BY(...) column list. If there are none + * then it is a syntax error. + */ + Query *largQuery; + Query *rargQuery; + List *matchingColumns; + List *matchingColumnsFiltered; + List *rightCorrespondingColumns; + ListCell *corrtl; + ListCell *mctl; + + /* Analyze left query to resolve column names. */ + largQuery = parse_sub_analyze((Node *) stmt->larg, + pstate, NULL,false, false ); + + /* Analyze right query to resolve column names. */ + rargQuery = parse_sub_analyze((Node *) stmt->rarg, + pstate, NULL, false, false ); + + /* + * Find matching columns from both queries. + * In CORRESPONDING BY, column names will be removed from + * matchingColumns if they are not in the BY clause. + * All columns in the BY clause must be in matchingColumns, + * otherwise raise syntax error in BY clause. + */ + matchingColumns = determineMatchingColumns(largQuery->targetList, + rargQuery->targetList); + + /* + * Every column name in correspondingClause must be in matchingColumns, + * otherwise it is a syntax error. + */ + foreach(corrtl, stmt->correspondingClause) + { + A_Const *corrtle = lfirst(corrtl); + Value *corrvalue = &corrtle->val; + char *name; + bool hasMatch = false; + + /* Get column name from correspondingClause. */ + name = strVal(corrvalue); + + foreach(mctl, matchingColumns) + { + TargetEntry *mctle = (TargetEntry *) lfirst(mctl); + + Assert(mctle->resname != NULL); + Assert(name != NULL); + + /* + * Compare correspondingClause column name with + * matchingColumns column names. + */ + if (strcmp(mctle->resname, name) == 0) + { + /* we have a match. */ + hasMatch = true; + break; + } + } + + if (!hasMatch) + { + /* + * CORRESPONDING BY clause contains a column name that is + * not in both tables. + */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column name \"%s\" cannot be used in corresponding by clause", name), + errhint("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.", + context), + parser_errposition(pstate, + exprLocation((Node *) stmt->correspondingClause)))); + } + } + + /* + * To preserve column ordering from correspondingClause and to remove + * columns from matchingColumns if they are not in correspondingClause, + * create a new list and finalize our column list for the + * CORRESPONDING BY clause. + */ + matchingColumnsFiltered = NIL; + + /* + * For each column in CORRESPONDING BY column list, check + * column existence in matchingColumns. + */ + foreach(corrtl, stmt->correspondingClause) + { + A_Const *corrtle = lfirst(corrtl); + Value *corrvalue = &corrtle->val; + + /* Get column name from correspondingClause. */ + char *name = strVal(corrvalue); + + foreach(mctl, matchingColumns) + { + TargetEntry *mctle = (TargetEntry *) lfirst(mctl); + + Assert(mctle->resname != NULL); + Assert(name != NULL); + + if (strcmp(mctle->resname, name) == 0) + { + /* we have a match.*/ + matchingColumnsFiltered = lappend( + matchingColumnsFiltered, mctle); + break; + } + } + } + + /* + * If matchingColumnsFiltered is empty, there is a semantic error. + * At least one column in the select lists must have the same name. + */ + Assert(list_length(matchingColumnsFiltered) > 0); + + /* + * there may be out-of-order resnos in corresponding target list + */ + op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered); + + /* + * matchingColumns contain target list as it appear in left query targetList + * we need matching column as it appear in right query targetlist inorder to + * make output column type for corresponding columns + */ + rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, + matchingColumnsFiltered); + /* + * make union'd datatype of output columns + */ + makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, + op, targetlist, pstate, context); + } + + /* * Recursively transform the left child node. */ op->larg = transformSetOperationTree(pstate, stmt->larg, @@ -1957,177 +2174,224 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, false, &rtargetlist); - /* - * Verify that the two children have the same number of non-junk - * columns, and determine the types of the merged output columns. - */ - if (list_length(ltargetlist) != list_length(rtargetlist)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("each %s query must have the same number of columns", - context), - parser_errposition(pstate, - exprLocation((Node *) rtargetlist)))); - - if (targetlist) - *targetlist = NIL; - op->colTypes = NIL; - op->colTypmods = NIL; - op->colCollations = NIL; - op->groupClauses = NIL; - forboth(ltl, ltargetlist, rtl, rtargetlist) + if (op->correspondingColumns == NIL ) { - TargetEntry *ltle = (TargetEntry *) lfirst(ltl); - TargetEntry *rtle = (TargetEntry *) lfirst(rtl); - Node *lcolnode = (Node *) ltle->expr; - Node *rcolnode = (Node *) rtle->expr; - Oid lcoltype = exprType(lcolnode); - Oid rcoltype = exprType(rcolnode); - int32 lcoltypmod = exprTypmod(lcolnode); - int32 rcoltypmod = exprTypmod(rcolnode); - Node *bestexpr; - int bestlocation; - Oid rescoltype; - int32 rescoltypmod; - Oid rescolcoll; - - /* select common type, same as CASE et al */ - rescoltype = select_common_type(pstate, - list_make2(lcolnode, rcolnode), - context, - &bestexpr); - bestlocation = exprLocation(bestexpr); - /* if same type and same typmod, use typmod; else default */ - if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) - rescoltypmod = lcoltypmod; - else - rescoltypmod = -1; - + makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate, + context); /* - * Verify the coercions are actually possible. If not, we'd fail - * later anyway, but we want to fail now while we have sufficient - * context to produce an error cursor position. - * - * For all non-UNKNOWN-type cases, we verify coercibility but we - * don't modify the child's expression, for fear of changing the - * child query's semantics. - * - * If a child expression is an UNKNOWN-type Const or Param, we - * want to replace it with the coerced expression. This can only - * happen when the child is a leaf set-op node. It's safe to - * replace the expression because if the child query's semantics - * depended on the type of this output column, it'd have already - * coerced the UNKNOWN to something else. We want to do this - * because (a) we want to verify that a Const is valid for the - * target type, or resolve the actual type of an UNKNOWN Param, - * and (b) we want to avoid unnecessary discrepancies between the - * output type of the child query and the resolved target type. - * Such a discrepancy would disable optimization in the planner. - * - * If it's some other UNKNOWN-type node, eg a Var, we do nothing - * (knowing that coerce_to_common_type would fail). The planner - * is sometimes able to fold an UNKNOWN Var to a constant before - * it has to coerce the type, so failing now would just break - * cases that might work. + * Verify that the two children have the same number of non-junk + * columns, and determine the types of the merged output columns. */ - if (lcoltype != UNKNOWNOID) - lcolnode = coerce_to_common_type(pstate, lcolnode, - rescoltype, context); - else if (IsA(lcolnode, Const) || - IsA(lcolnode, Param)) - { - lcolnode = coerce_to_common_type(pstate, lcolnode, - rescoltype, context); - ltle->expr = (Expr *) lcolnode; - } + if (list_length(ltargetlist) != list_length(rtargetlist)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("each %s query must have the same number of columns", context), + parser_errposition(pstate, + exprLocation((Node *) rtargetlist)))); + } - if (rcoltype != UNKNOWNOID) - rcolnode = coerce_to_common_type(pstate, rcolnode, - rescoltype, context); - else if (IsA(rcolnode, Const) || - IsA(rcolnode, Param)) - { - rcolnode = coerce_to_common_type(pstate, rcolnode, - rescoltype, context); - rtle->expr = (Expr *) rcolnode; - } + return (Node *) op; + } +} - /* - * Select common collation. A common collation is required for - * all set operators except UNION ALL; see SQL:2008 7.13 <query - * expression> Syntax Rule 15c. (If we fail to identify a common - * collation for a UNION ALL column, the curCollations element - * will be set to InvalidOid, which may result in a runtime error - * if something at a higher query level wants to use the column's - * collation.) - */ - rescolcoll = select_common_collation(pstate, - list_make2(lcolnode, rcolnode), - (op->op == SETOP_UNION && op->all)); +/* + * Processes targetlists of two queries for column equivalence to use + * with UNION/INTERSECT/EXCEPT CORRESPONDING. + */ +static List * +determineMatchingColumns(List *ltargetlist, List *rtargetlist) +{ + List *matchingColumns = NIL; + ListCell *ltl; + ListCell *rtl; + + foreach(ltl, ltargetlist) + { + foreach(rtl, rtargetlist) + { + TargetEntry *ltle = (TargetEntry *) lfirst(ltl); + TargetEntry *rtle = (TargetEntry *) lfirst(rtl); - /* emit results */ - op->colTypes = lappend_oid(op->colTypes, rescoltype); - op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); - op->colCollations = lappend_oid(op->colCollations, rescolcoll); + elog(DEBUG4, "%s", ltle->resname); - /* - * For all cases except UNION ALL, identify the grouping operators - * (and, if available, sorting operators) that will be used to - * eliminate duplicates. - */ - if (op->op != SETOP_UNION || !op->all) - { - SortGroupClause *grpcl = makeNode(SortGroupClause); - Oid sortop; - Oid eqop; - bool hashable; - ParseCallbackState pcbstate; - - setup_parser_errposition_callback(&pcbstate, pstate, - bestlocation); - - /* determine the eqop and optional sortop */ - get_sort_group_operators(rescoltype, - false, true, false, - &sortop, &eqop, NULL, - &hashable); - - cancel_parser_errposition_callback(&pcbstate); - - /* we don't have a tlist yet, so can't assign sortgrouprefs */ - grpcl->tleSortGroupRef = 0; - grpcl->eqop = eqop; - grpcl->sortop = sortop; - grpcl->nulls_first = false; /* OK with or without sortop */ - grpcl->hashable = hashable; - - op->groupClauses = lappend(op->groupClauses, grpcl); - } + /* Names of the columns must be resolved before calling this method. */ + Assert(ltle->resname != NULL); + Assert(rtle->resname != NULL); - /* - * Construct a dummy tlist entry to return. We use a SetToDefault - * node for the expression, since it carries exactly the fields - * needed, but any other expression node type would do as well. - */ - if (targetlist) + /* If column names are the same, append it to the result. */ + if (strcmp(ltle->resname, rtle->resname) == 0) { - SetToDefault *rescolnode = makeNode(SetToDefault); - TargetEntry *restle; - - rescolnode->typeId = rescoltype; - rescolnode->typeMod = rescoltypmod; - rescolnode->collation = rescolcoll; - rescolnode->location = bestlocation; - restle = makeTargetEntry((Expr *) rescolnode, - 0, /* no need to set resno */ - NULL, - false); - *targetlist = lappend(*targetlist, restle); + matchingColumns = lappend(matchingColumns, ltle); + continue; } } + } - return (Node *) op; + return matchingColumns; +} + +/* + * process right and left target list to set up union'd datatype + */ +static void * +makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op, + List **targetlist, ParseState *pstate, const char *context) +{ + ListCell *ltl; + ListCell *rtl; + + if (targetlist) + *targetlist = NIL; + + op->colTypes = NIL; + op->colTypmods = NIL; + op->colCollations = NIL; + op->groupClauses = NIL; + + forboth(ltl, ltargetlist, rtl, rtargetlist) + { + TargetEntry *ltle = (TargetEntry *) lfirst(ltl); + TargetEntry *rtle = (TargetEntry *) lfirst(rtl); + Node *lcolnode = (Node *) ltle->expr; + Node *rcolnode = (Node *) rtle->expr; + Oid lcoltype = exprType(lcolnode); + Oid rcoltype = exprType(rcolnode); + int32 lcoltypmod = exprTypmod(lcolnode); + int32 rcoltypmod = exprTypmod(rcolnode); + Node *bestexpr; + int bestlocation; + Oid rescoltype; + int32 rescoltypmod; + Oid rescolcoll; + + /* select common type, same as CASE et al */ + rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode), + context, &bestexpr); + bestlocation = exprLocation(bestexpr); + /* if same type and same typmod, use typmod; else default */ + if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) + rescoltypmod = lcoltypmod; + else + rescoltypmod = -1; + + /* + * Verify the coercions are actually possible. If not, we'd fail + * later anyway, but we want to fail now while we have sufficient + * context to produce an error cursor position. + * + * For all non-UNKNOWN-type cases, we verify coercibility but we + * don't modify the child's expression, for fear of changing the + * child query's semantics. + * + * If a child expression is an UNKNOWN-type Const or Param, we + * want to replace it with the coerced expression. This can only + * happen when the child is a leaf set-op node. It's safe to + * replace the expression because if the child query's semantics + * depended on the type of this output column, it'd have already + * coerced the UNKNOWN to something else. We want to do this + * because (a) we want to verify that a Const is valid for the + * target type, or resolve the actual type of an UNKNOWN Param, + * and (b) we want to avoid unnecessary discrepancies between the + * output type of the child query and the resolved target type. + * Such a discrepancy would disable optimization in the planner. + * + * If it's some other UNKNOWN-type node, eg a Var, we do nothing + * (knowing that coerce_to_common_type would fail). The planner + * is sometimes able to fold an UNKNOWN Var to a constant before + * it has to coerce the type, so failing now would just break + * cases that might work. + */ + if (lcoltype != UNKNOWNOID) + lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, + context); + else if (IsA(lcolnode, Const) || IsA(lcolnode, Param)) + { + lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype, + context); + ltle->expr = (Expr *) lcolnode; + } + + if (rcoltype != UNKNOWNOID) + rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, + context); + else if (IsA(rcolnode, Const) || IsA(rcolnode, Param)) + { + rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype, + context); + rtle->expr = (Expr *) rcolnode; + } + + /* + * Select common collation. A common collation is required for + * all set operators except UNION ALL; see SQL:2008 7.13 <query + * expression> Syntax Rule 15c. (If we fail to identify a common + * collation for a UNION ALL column, the curCollations element + * will be set to InvalidOid, which may result in a runtime error + * if something at a higher query level wants to use the column's + * collation.) + */ + rescolcoll = select_common_collation(pstate, + list_make2(lcolnode, rcolnode), + (op->op == SETOP_UNION && op->all)); + + /* emit results */ + op->colTypes = lappend_oid(op->colTypes, rescoltype); + op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); + op->colCollations = lappend_oid(op->colCollations, rescolcoll); + + /* + * For all cases except UNION ALL, identify the grouping operators + * (and, if available, sorting operators) that will be used to + * eliminate duplicates. + */ + if (op->op != SETOP_UNION || !op->all) + { + SortGroupClause *grpcl = makeNode(SortGroupClause); + Oid sortop; + Oid eqop; + bool hashable; + ParseCallbackState pcbstate; + + setup_parser_errposition_callback(&pcbstate, pstate, bestlocation); + + /* determine the eqop and optional sortop */ + get_sort_group_operators(rescoltype, false, true, false, &sortop, + &eqop, NULL, &hashable); + + cancel_parser_errposition_callback(&pcbstate); + + /* we don't have a tlist yet, so can't assign sortgrouprefs */ + grpcl->tleSortGroupRef = 0; + grpcl->eqop = eqop; + grpcl->sortop = sortop; + grpcl->nulls_first = false; /* OK with or without sortop */ + grpcl->hashable = hashable; + + op->groupClauses = lappend(op->groupClauses, grpcl); + } + + /* + * Construct a dummy tlist entry to return. We use a SetToDefault + * node for the expression, since it carries exactly the fields + * needed, but any other expression node type would do as well. + */ + if (targetlist) + { + SetToDefault *rescolnode = makeNode(SetToDefault); + TargetEntry *restle; + + rescolnode->typeId = rescoltype; + rescolnode->typeMod = rescoltypmod; + rescolnode->collation = rescolcoll; + rescolnode->location = bestlocation; + + /* no need to set resno */ + restle = makeTargetEntry((Expr *) rescolnode, 0, + NULL, false ); + *targetlist = lappend(*targetlist, restle); + } } + return 0; } /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6316688a88..6e10718ccc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt, Node *limitOffset, Node *limitCount, WithClause *withClause, core_yyscan_t yyscanner); -static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg); +static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg); static Node *doNegate(Node *n, int location); static void doNegateFloat(Value *v); static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location); @@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); relation_expr_list dostmt_opt_list transform_element_list transform_type_list TriggerTransitions TriggerReferencing - publication_name_list + publication_name_list opt_corresponding_clause corresponding_list %type <list> group_by_list %type <node> group_by_item empty_grouping_set rollup_clause cube_clause @@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT - CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE + CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE CROSS CSV CUBE CURRENT_P CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -10752,20 +10752,26 @@ simple_select: n->fromClause = list_make1($2); $$ = (Node *)n; } - | select_clause UNION all_or_distinct select_clause + | select_clause UNION all_or_distinct opt_corresponding_clause select_clause { - $$ = makeSetOp(SETOP_UNION, $3, $1, $4); + $$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5); } - | select_clause INTERSECT all_or_distinct select_clause + | select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause { - $$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4); + $$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5); } - | select_clause EXCEPT all_or_distinct select_clause + | select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause { - $$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4); + $$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5); } ; +opt_corresponding_clause: + CORRESPONDING BY '(' corresponding_list ')' { $$ = $4; } + | CORRESPONDING { $$ = list_make1(NIL); } + | /*EMPTY*/ { $$ = NIL; } + ; + /* * SQL standard WITH clause looks like: * @@ -14107,6 +14113,13 @@ name_list: name ; +corresponding_list: ColId + { $$ = list_make1(makeStringConst($1, @1)); } + | corresponding_list ',' ColId + { $$ = lappend($1, makeStringConst($3, @1)); } + ; + + name: ColId { $$ = $1; }; database_name: @@ -14423,6 +14436,7 @@ unreserved_keyword: | CONTINUE_P | CONVERSION_P | COPY + | CORRESPONDING | COST | CSV | CUBE @@ -15255,7 +15269,7 @@ insertSelectOptions(SelectStmt *stmt, } static Node * -makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg) +makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg) { SelectStmt *n = makeNode(SelectStmt); @@ -15263,6 +15277,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg) n->all = all; n->larg = (SelectStmt *) larg; n->rarg = (SelectStmt *) rarg; + n->correspondingClause = correspondingClause; return (Node *) n; } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 3b84140a9b..90eb4e48aa 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1910,3 +1910,25 @@ FigureColnameInternal(Node *node, char **name) return strength; } + +/* + * orderCorrespondingList() + * order target list resno . + */ +List * +orderCorrespondingList(List *targetlist) +{ + List *p_target = NIL; + ListCell *o_target; + int pos = 1; + + foreach(o_target, targetlist) + { + TargetEntry *tar = (TargetEntry *) lfirst(o_target); + + p_target = lappend(p_target, + makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false)); + } + + return p_target; +} diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 0d7a2b1e1b..b553d847d8 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -735,7 +735,8 @@ typeStringToTypeName(const char *str) stmt->limitCount != NULL || stmt->lockingClause != NIL || stmt->withClause != NULL || - stmt->op != SETOP_NONE) + stmt->op != SETOP_NONE || + stmt->correspondingClause != NULL) goto fail; if (list_length(stmt->targetList) != 1) goto fail; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d576523f6a..fd1883f903 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1459,7 +1459,9 @@ typedef struct SelectStmt * These fields are used only in "leaf" SelectStmts. */ List *distinctClause; /* NULL, list of DISTINCT ON exprs, or - * lcons(NIL,NIL) for all (SELECT DISTINCT) */ + * lcons(NIL,NIL) for all (SELECT DISTINCT) */ + List *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */ + * lcons(NIL, NIL) for CORRESPONDING */ IntoClause *intoClause; /* target for SELECT INTO */ List *targetList; /* the target list (of ResTarget) */ List *fromClause; /* the FROM clause */ @@ -1525,7 +1527,7 @@ typedef struct SetOperationStmt bool all; /* ALL specified? */ Node *larg; /* left child */ Node *rarg; /* right child */ - /* Eventually add fields for CORRESPONDING spec here */ + List *correspondingColumns; /* list of column names (A_Const) */ /* Fields derived during parse analysis: */ List *colTypes; /* OID list of output column type OIDs */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 28c4dab258..36ada7928a 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD) PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD) PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD) PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD) +PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD) PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD) PG_KEYWORD("create", CREATE, RESERVED_KEYWORD) PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD) diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h index d06a235df0..32b9cfda17 100644 --- a/src/include/parser/parse_target.h +++ b/src/include/parser/parse_target.h @@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var, int levelsup); extern char *FigureColname(Node *node); extern char *FigureIndexColname(Node *node); +extern List *orderCorrespondingList(List *targetlist); #endif /* PARSE_TARGET_H */ diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 4d697bada7..e4ce1b9e13 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -59,6 +59,221 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1; 2.2 (2 rows) +SELECT 1 AS two UNION CORRESPONDING SELECT 2 two; + two +----- + 1 + 2 +(2 rows) + +SELECT 1 AS one UNION CORRESPONDING SELECT 1 one; + one +----- + 1 +(1 row) + +SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two; + two +----- + 1 + 2 +(2 rows) + +SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two; + two +----- + 1 + 1 +(2 rows) + +SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two; + two +----- + 2 + 1 +(2 rows) + +SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three; + three +------- + 1 + 2 + 2 +(3 rows) + +SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three; + three +------- + 3 + 2 + 1 +(3 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b; + c | b | a +---+---+--- + 3 | 2 | 1 + 6 | 5 | 4 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c; + a +--- + 1 + 4 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c; + b +--- + 2 + 5 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c; + c +--- + 3 + 6 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c; + a | b +---+--- + 1 | 2 + 4 | 5 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c; + b | c +---+--- + 2 | 3 + 5 | 6 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c; + a | c +---+--- + 1 | 3 + 4 | 6 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d; + c | b +---+--- + 3 | 2 + 6 | 5 +(2 rows) + +-- CORRESPONDING column ordering, left clause's column ordering must be preserved. +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + b | a | c +---+---+--- + 2 | 1 | 3 + 5 | 4 | 6 +(2 rows) + +SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + a | c | b +---+---+--- + 1 | 3 | 2 + 4 | 6 | 5 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a; + b | a | c +---+---+--- + 2 | 1 | 3 + 5 | 4 | 6 +(2 rows) + +-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved. +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c; + b | c | a +---+---+--- + 2 | 3 | 1 + 5 | 6 | 4 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c; + c | a | b +---+---+--- + 3 | 1 | 2 + 6 | 4 | 5 +(2 rows) + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a; + a | b | c +---+---+--- + 1 | 2 | 3 + 4 | 5 | 6 +(2 rows) + +SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c; + b | c | a +---+---+--- + 2 | 3 | 1 + 5 | 6 | 4 +(2 rows) + +SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c; + c | a | b +---+---+--- + 3 | 1 | 2 + 6 | 4 | 5 +(2 rows) + +-- should to fail +SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d; +ERROR: there are not any corresponding name +LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d; + ^ +HINT: UNION queries with a CORRESPONDING clause must have at least one column with the same name +SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d; +ERROR: column name "a" cannot be used in corresponding by clause +LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10... + ^ +HINT: UNION queries with a CORRESPONDING BY clause must only contain column names from both tables. +SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d; +ERROR: column name "x" cannot be used in corresponding by clause +LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c... + ^ +HINT: UNION queries with a CORRESPONDING BY clause must only contain column names from both tables. -- Mixed types SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1; two @@ -258,6 +473,108 @@ ORDER BY 1; hi de ho neighbor (5 rows) +SELECT f1 AS five FROM FLOAT8_TBL +UNION CORRESPONDING +SELECT f1 AS five FROM FLOAT8_TBL +ORDER BY 1; + five +----------------------- + -1.2345678901234e+200 + -1004.3 + -34.84 + -1.2345678901234e-200 + 0 +(5 rows) + +SELECT f1 AS five FROM FLOAT8_TBL +UNION CORRESPONDING BY(five) +SELECT f1 AS five FROM FLOAT8_TBL +ORDER BY 1; + five +----------------------- + -1.2345678901234e+200 + -1004.3 + -34.84 + -1.2345678901234e-200 + 0 +(5 rows) + +SELECT f1 AS ten FROM FLOAT8_TBL +UNION ALL CORRESPONDING +SELECT f1 AS ten FROM FLOAT8_TBL; + ten +----------------------- + 0 + -34.84 + -1004.3 + -1.2345678901234e+200 + -1.2345678901234e-200 + 0 + -34.84 + -1004.3 + -1.2345678901234e+200 + -1.2345678901234e-200 +(10 rows) + +SELECT f1 AS nine FROM FLOAT8_TBL +UNION CORRESPONDING +SELECT f1 AS nine FROM INT4_TBL +ORDER BY 1; + nine +----------------------- + -1.2345678901234e+200 + -2147483647 + -123456 + -1004.3 + -34.84 + -1.2345678901234e-200 + 0 + 123456 + 2147483647 +(9 rows) + +SELECT f1 AS ten FROM FLOAT8_TBL +UNION ALL CORRESPONDING +SELECT f1 AS ten FROM INT4_TBL; + ten +----------------------- + 0 + -34.84 + -1004.3 + -1.2345678901234e+200 + -1.2345678901234e-200 + 0 + 123456 + -123456 + 2147483647 + -2147483647 +(10 rows) + +SELECT f1 AS five FROM FLOAT8_TBL + WHERE f1 BETWEEN -1e6 AND 1e6 +UNION CORRESPONDING +SELECT f1 AS five FROM INT4_TBL + WHERE f1 BETWEEN 0 AND 1000000; + five +----------------------- + -1004.3 + -34.84 + -1.2345678901234e-200 + 0 + 123456 +(5 rows) + +SELECT * FROM FLOAT8_TBL +UNION corresponding SELECT * FROM FLOAT8_TBL; + f1 +----------------------- + 0 + -1.2345678901234e-200 + -34.84 + -1.2345678901234e+200 + -1004.3 +(5 rows) + -- -- INTERSECT and EXCEPT -- @@ -320,6 +637,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1; SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE; ERROR: FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT +SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl; + q2 +------------------ + 123 + 4567890123456789 +(2 rows) + +SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl; + q2 +------------------ + 123 + 4567890123456789 + 4567890123456789 +(3 rows) + +SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1; + q2 +------------------- + -4567890123456789 + 456 +(2 rows) + +SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1; + q2 +------------------- + -4567890123456789 + 456 +(2 rows) + +SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1; + q2 +------------------- + -4567890123456789 + 456 + 4567890123456789 +(3 rows) + +SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl; + q1 +---- +(0 rows) + +SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl; + q1 +------------------ + 123 + 4567890123456789 +(2 rows) + +SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl; + q1 +------------------ + 123 + 4567890123456789 + 4567890123456789 +(3 rows) + -- -- Mixed types -- @@ -338,6 +712,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1; -1.2345678901234e-200 (4 rows) +SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl; + f1 +---- + 0 +(1 row) + +SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1; + f1 +----------------------- + -1.2345678901234e+200 + -1004.3 + -34.84 + -1.2345678901234e-200 +(4 rows) + -- -- Operator precedence and (((((extra))))) parentheses -- @@ -424,6 +813,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) 4567890123456789 (2 rows) +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl +ORDER BY q2,q1; + q1 | q2 +----+---- +(0 rows) + +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl +ORDER BY q1; + q1 +---- +(0 rows) + +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl +ORDER BY q2; + q2 +---- +(0 rows) + -- -- New syntaxes (7.1) permit new tests -- @@ -459,6 +866,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo'; ERROR: invalid input syntax for type numeric: "foo" LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo'; ^ +SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a; +ERROR: invalid input syntax for type numeric: "foo" +LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a... + ^ +SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a +UNION CORRESPONDING +SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b +ORDER BY 1; + f1 +------ + a + ab + abcd + test +(4 rows) + -- -- Test that expression-index constraints can be pushed down through -- UNION or UNION ALL diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql index 48e6850798..b0f3152ef1 100644 --- a/src/test/regress/sql/union.sql +++ b/src/test/regress/sql/union.sql @@ -20,6 +20,74 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1; SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1; +SELECT 1 AS two UNION CORRESPONDING SELECT 2 two; + +SELECT 1 AS one UNION CORRESPONDING SELECT 1 one; + +SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two; + +SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two; + +SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two; + +SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three; + +SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + +SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d; + +-- CORRESPONDING column ordering, left clause's column ordering must be preserved. + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + +SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a; + +SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a; + +-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved. + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c; + +SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a; + +SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c; + +SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c; + + +-- should to fail +SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d; + +SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d; + +SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d; + -- Mixed types SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1; @@ -90,6 +158,38 @@ UNION SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL ORDER BY 1; +SELECT f1 AS five FROM FLOAT8_TBL +UNION CORRESPONDING +SELECT f1 AS five FROM FLOAT8_TBL +ORDER BY 1; + +SELECT f1 AS five FROM FLOAT8_TBL +UNION CORRESPONDING BY(five) +SELECT f1 AS five FROM FLOAT8_TBL +ORDER BY 1; + +SELECT f1 AS ten FROM FLOAT8_TBL +UNION ALL CORRESPONDING +SELECT f1 AS ten FROM FLOAT8_TBL; + +SELECT f1 AS nine FROM FLOAT8_TBL +UNION CORRESPONDING +SELECT f1 AS nine FROM INT4_TBL +ORDER BY 1; + +SELECT f1 AS ten FROM FLOAT8_TBL +UNION ALL CORRESPONDING +SELECT f1 AS ten FROM INT4_TBL; + +SELECT f1 AS five FROM FLOAT8_TBL + WHERE f1 BETWEEN -1e6 AND 1e6 +UNION CORRESPONDING +SELECT f1 AS five FROM INT4_TBL + WHERE f1 BETWEEN 0 AND 1000000; + +SELECT * FROM FLOAT8_TBL +UNION corresponding SELECT * FROM FLOAT8_TBL; + -- -- INTERSECT and EXCEPT -- @@ -112,6 +212,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1; SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE; +SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl; + +SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl; + +SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1; + +SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1; + +SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1; + +SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl; + +SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl; + +SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl; + -- -- Mixed types -- @@ -120,6 +236,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1; SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1; +SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl; + +SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1; + -- -- Operator precedence and (((((extra))))) parentheses -- @@ -150,6 +270,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1; -- But this should work: SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1; +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl +ORDER BY q2,q1; + +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl +ORDER BY q1; + +SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl +ORDER BY q2; + -- -- New syntaxes (7.1) permit new tests -- @@ -170,6 +299,13 @@ ORDER BY 1; -- This should fail, but it should produce an error cursor SELECT '3.4'::numeric UNION SELECT 'foo'; +SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a; + +SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a +UNION CORRESPONDING +SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b +ORDER BY 1; + -- -- Test that expression-index constraints can be pushed down through -- UNION or UNION ALL
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers