diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..2d60718 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (expression , EXCEPT
+ CORRESPONDING
+
+
set union
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (expression ,
-query1 UNION ALL query2
-query1 INTERSECT ALL query2
-query1 EXCEPT ALL query2
+query1 UNION ALL CORRESPONDING BY query2
+query1 INTERSECT ALL CORRESPONDING BY query2
+query1 EXCEPT ALL CORRESPONDING BY query2
query1 and
query2 are queries that can use any of
@@ -1659,15 +1662,32 @@ SELECT DISTINCT ON (expression ,
- In order to calculate the union, intersection, or difference of two
- queries, the two queries must be union compatible
,
- which means that they return the same number of columns and
- the corresponding columns have compatible data types, as
- described in .
+ EXCEPT> returns all rows that are in the result of
+ query1 but not in the result of
+ query2. (This is sometimes called the
+ difference> between two queries.) Again, duplicates
+ are eliminated unless EXCEPT ALL> is used.
+
+
+
+ CORRESPONDING> returns all columns that are in both
+ query1> and query2> with the same name.
+
+
+
+ CORRESPONDING BY> returns all columns in the column list
+ that are also in both query1> and
+ query2> with the same name. The names in column list
+ must be unique.
+
+
+
+ The names of columns in result when CORRESPONDING> or
+ CORRESPONDING BY> clause is used must be unique in
+ query1 and query2.
-
Sorting Rows
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( expressioncondition ]
[ GROUP BY expression [, ...] ]
[ HAVING condition [, ...] ]
- [ { UNION | INTERSECT | EXCEPT } [ ALL ] select ]
+ [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( expression ) ] ] select ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ LIMIT { count | ALL } ]
[ OFFSET start ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..41f5155 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,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);
@@ -2885,6 +2886,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
COPY_SCALAR_FIELD(all);
COPY_NODE_FIELD(larg);
COPY_NODE_FIELD(rarg);
+ COPY_NODE_FIELD(correspondingColumns);
+ COPY_SCALAR_FIELD(hasCorrespondingBy);
COPY_NODE_FIELD(colTypes);
COPY_NODE_FIELD(colTypmods);
COPY_NODE_FIELD(colCollations);
@@ -4442,6 +4445,8 @@ _copyValue(const Value *from)
(int) from->type);
break;
}
+ COPY_LOCATION_FIELD(location);
+
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..8483939 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,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);
@@ -1033,6 +1034,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
COMPARE_SCALAR_FIELD(all);
COMPARE_NODE_FIELD(larg);
COMPARE_NODE_FIELD(rarg);
+ COMPARE_NODE_FIELD(correspondingColumns);
+ COMPARE_SCALAR_FIELD(hasCorrespondingBy);
COMPARE_NODE_FIELD(colTypes);
COMPARE_NODE_FIELD(colTypmods);
COMPARE_NODE_FIELD(colCollations);
@@ -2857,6 +2860,8 @@ _equalValue(const Value *a, const Value *b)
break;
}
+ COMPARE_LOCATION_FIELD(location);
+
return true;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,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 1560ac3..46e60e8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,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);
}
@@ -2833,6 +2834,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
WRITE_BOOL_FIELD(all);
WRITE_NODE_FIELD(larg);
WRITE_NODE_FIELD(rarg);
+ WRITE_NODE_FIELD(correspondingColumns);
+ WRITE_BOOL_FIELD(hasCorrespondingBy);
WRITE_NODE_FIELD(colTypes);
WRITE_NODE_FIELD(colTypmods);
WRITE_NODE_FIELD(colCollations);
@@ -3007,6 +3010,7 @@ _outAExpr(StringInfo str, const A_Expr *node)
static void
_outValue(StringInfo str, const Value *value)
{
+ /* NB: this isn't a complete set of fields */
switch (value->type)
{
case T_Integer:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..bf72f52 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,8 @@ _readSetOperationStmt(void)
READ_BOOL_FIELD(all);
READ_NODE_FIELD(larg);
READ_NODE_FIELD(rarg);
+ READ_NODE_FIELD(correspondingColumns);
+ READ_BOOL_FIELD(hasCorrespondingBy);
READ_NODE_FIELD(colTypes);
READ_NODE_FIELD(colTypmods);
READ_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5d2f96c..72afc17 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -26,6 +26,7 @@ makeInteger(long i)
v->type = T_Integer;
v->val.ival = i;
+ v->location = -1;
return v;
}
@@ -41,6 +42,7 @@ makeFloat(char *numericStr)
v->type = T_Float;
v->val.str = numericStr;
+ v->location = -1;
return v;
}
@@ -56,6 +58,7 @@ makeString(char *str)
v->type = T_String;
v->val.str = str;
+ v->location = -1;
return v;
}
@@ -71,5 +74,6 @@ makeBitString(char *str)
v->type = T_BitString;
v->val.str = str;
+ v->location = -1;
return v;
}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..519c921 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;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
rtlc = lnext(rtlc);
- Assert(inputtle->resno == resno);
- Assert(reftle->resno == resno);
+ Assert(!no_corresponding || inputtle->resno == resno);
+ Assert(!no_corresponding || reftle->resno == resno);
Assert(!inputtle->resjunk);
Assert(!reftle->resjunk);
@@ -2112,3 +2165,70 @@ 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);
+
+ /* 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 0f7659b..20b780e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
+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
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+ ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+ ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+ ParseState *pstate, const char *context);
/*
* parse_analyze
@@ -1664,7 +1672,37 @@ 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);
+ /*
+ * In the case of corresponding without by clause property across
+ * the statement may differ
+ */
+ if (!sostmt->hasCorrespondingBy)
+ {
+ Node *correspodning_node;
+ correspodning_node = sostmt->larg;
+ while (correspodning_node && IsA(correspodning_node, SetOperationStmt))
+ {
+ SetOperationStmt *op = (SetOperationStmt *) correspodning_node;
+ op->correspondingColumns = sostmt->correspondingColumns;
+ op->colTypes = sostmt->colTypes;
+ op->colTypmods = sostmt->colTypmods;
+ op->colCollations = sostmt->colCollations;
+ op->groupClauses = sostmt->groupClauses;
+
+ correspodning_node = op->larg;
+ }
+ }
+ }
+ else
+ left_tlist = list_head(leftmostQuery->targetList);
forthree(lct, sostmt->colTypes,
lcm, sostmt->colTypmods,
@@ -1924,8 +1962,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" :
@@ -1936,6 +1972,84 @@ 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;
+ op->hasCorrespondingBy = false;
+ }
+ else
+ {
+ /*
+ * 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;
+
+ op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+ /* 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 = CommonColumns(largQuery->targetList,
+ rargQuery->targetList,
+ op->hasCorrespondingBy,
+ pstate,
+ context);
+
+ /*
+ * If matchingColumns is empty, there is an error.
+ * At least one column in the select lists must have the same name.
+ */
+ if (matchingColumns == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("there is 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)))));
+
+ /* Use column filter when it is known */
+ if (op->hasCorrespondingBy)
+ matchingColumns = FilterColumnsByNames(matchingColumns,
+ stmt->correspondingClause,
+ pstate,
+ context);
+
+ op->correspondingColumns = matchingColumns;
+
+ /*
+ * When we know matching columns, we can quickly create
+ * corresponding target list for right target list. It is faster,
+ * than using symmetry. Ensure unique columns when hasCorrespondingBy
+ * is true - in this case, the uniq is not checked already.
+ */
+ rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+ matchingColumns,
+ op->hasCorrespondingBy,
+ pstate,
+ context);
+
+ /* make union'd datatype of output column */
+ makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+ op, targetlist, pstate, context);
+ }
+
+ /*
* Recursively transform the left child node.
*/
op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1960,173 +2074,20 @@ 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;
-
- /*
- * 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 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;
- restle = makeTargetEntry((Expr *) rescolnode,
- 0, /* no need to set resno */
- NULL,
- false);
- *targetlist = lappend(*targetlist, restle);
- }
+ makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+ context);
+ /*
+ * 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))));
}
return (Node *) op;
@@ -2134,6 +2095,397 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
}
/*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+ ParseState *pstate, const char *context)
+{
+ List *common_columns = NIL;
+ ListCell *ltlc;
+ ListCell *rtlc;
+ int resno = 1;
+
+ foreach(ltlc, ltargetlist)
+ {
+ TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+ bool found = false;
+
+ Assert(lte->resname != NULL);
+
+ foreach(rtlc, rtargetlist)
+ {
+ ListCell *lc;
+ TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+ Assert(rte->resname != NULL);
+
+ if (strcmp(lte->resname, rte->resname) == 0)
+ {
+ if (filtered)
+ {
+ /*
+ * We found common column, but we don't know if it
+ * is in CORRESPONDING BY list - so don't try do more
+ * work here. The column list will be modified later,
+ * so use shall copy here.
+ */
+ common_columns = lappend(common_columns, lte);
+ break;
+ }
+
+ /* If same column name mentioned more than once it is syntax error . */
+ if (found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("corresponding column \"%s\" is used more times", rte->resname),
+ errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) rte))));
+
+ found = true;
+
+ /* In this case, common_columns must be unique */
+ foreach(lc, common_columns)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+ if (strcmp(te->resname, lte->resname) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("corresponding column \"%s\" is used more times", lte->resname),
+ errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) lte))));
+ }
+
+ /* When is not any other filter create final te */
+ common_columns = lappend(common_columns,
+ makeTargetEntry(lte->expr,
+ (AttrNumber) resno++,
+ lte->resname,
+ false));
+ }
+ }
+ }
+
+ return common_columns;
+}
+
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+ ParseState *pstate, const char *context)
+{
+ List *filtered_columns = NIL;
+ ListCell *flc;
+ int resno = 1;
+
+ Assert(common_columns != NIL);
+ Assert(filter != NIL);
+
+ foreach(flc, filter)
+ {
+ Value *strval = (Value *) lfirst(flc);
+ char *name = strVal(strval);
+ ListCell *tlc;
+ bool found = false;
+
+ foreach(tlc, common_columns)
+ {
+ TargetEntry *tec = (TargetEntry *) lfirst(tlc);
+
+ if (strcmp(tec->resname, name) == 0)
+ {
+ ListCell *lc;
+
+ /*
+ * When "found" is true, then common_columns contains
+ * duplicate columns. Raise exception then.
+ */
+ if (found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("corresponding column \"%s\" is used more times", name),
+ errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) tec))));
+
+ found = true;
+
+ /* result list should not to contains this name */
+ foreach(lc, filtered_columns)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+ /*
+ * CORRESPONDING BY clause contains a column name that is
+ * not in unique in this clause
+ */
+ if (strcmp(te->resname, name) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+ errhint("CORRESPONDING BY clause must contain unique column names only."),
+ parser_errposition(pstate, strval->location)));
+ }
+
+ /* create te with correct resno */
+ filtered_columns = lappend(filtered_columns,
+ makeTargetEntry(tec->expr,
+ (AttrNumber) resno++,
+ tec->resname,
+ false));
+ }
+ }
+
+ /*
+ * CORRESPONDING BY clause contains a column name that is not
+ * in common columns.
+ */
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+ errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+ context),
+ parser_errposition(pstate, strval->location)));
+ }
+
+ return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When filtered is true, filtered columns in target list are
+ * unique.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+ ParseState *pstate, const char *context)
+{
+ List *result = NIL;
+ ListCell *lc;
+ int resno = 1;
+
+ foreach(lc, filter)
+ {
+ TargetEntry *fte = (TargetEntry *) lfirst(lc);
+ ListCell *tle;
+ bool found = false;
+
+ foreach(tle, targetlist)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(tle);
+
+ if (strcmp(fte->resname, te->resname) == 0)
+ {
+ /* create te with correct resno */
+ result = lappend(result,
+ makeTargetEntry(te->expr,
+ (AttrNumber) resno++,
+ te->resname,
+ false));
+
+ if (!check_uniq)
+ break;
+
+ /*
+ * When "found" is true, then targetlist contains
+ * duplicate filtered columns. Raise exception then.
+ */
+ if (found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("corresponding column \"%s\" is used more times", te->resname),
+ errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) te))));
+
+ found = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * 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 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);
+ }
+ }
+
+}
+
+/*
* Process the outputs of the non-recursive term of a recursive union
* to set up the parent CTE's columns
*/
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..577cc2a 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
%type group_by_list
%type group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLLATION COLUMN 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
@@ -3578,7 +3578,10 @@ columnList:
columnElem: ColId
{
- $$ = (Node *) makeString($1);
+ Value *v = makeString($1);
+
+ v->location = @1;
+ $$ = (Node *) v;
}
;
@@ -10808,20 +10811,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 '(' columnList ')' { $$ = $4; }
+ | CORRESPONDING { $$ = list_make1(NIL); }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
/*
* SQL standard WITH clause looks like:
*
@@ -13999,7 +14008,6 @@ name_list: name
{ $$ = lappend($1, makeString($3)); }
;
-
name: ColId { $$ = $1; };
database_name:
@@ -14315,6 +14323,7 @@ unreserved_keyword:
| CONTINUE_P
| CONVERSION_P
| COPY
+ | CORRESPONDING
| COST
| CSV
| CUBE
@@ -15145,7 +15154,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);
@@ -15153,6 +15162,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_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,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/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f355954..b342961 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5349,6 +5349,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
}
if (op->all)
appendStringInfoString(buf, "ALL ");
+ if (op->correspondingColumns != NIL )
+ {
+ if (op->hasCorrespondingBy)
+ {
+ const char *sep;
+ ListCell *l;
+ appendStringInfoString(buf, "CORRESPONDING BY(");
+ sep = "";
+
+ foreach(l, op->correspondingColumns)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ appendStringInfoString(buf, sep);
+ appendStringInfo(buf, "%s", tle->resname);
+ sep = ", ";
+ }
+ appendStringInfoChar(buf, ')');
+
+ }
+ else
+
+ appendStringInfoString(buf, "CORRESPONDING ");
+ }
/* Always parenthesize if RHS is another setop */
need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..f95accc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1421,6 +1421,7 @@ typedef struct SelectStmt
*/
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+ List *correspondingClause; /* CORRESPONDING BY clauses*/
IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
@@ -1456,7 +1457,6 @@ typedef struct SelectStmt
bool all; /* ALL specified? */
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
- /* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;
@@ -1486,8 +1486,8 @@ 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 corresponding column names */
+ bool hasCorrespondingBy; /* has corresponding by cluase? */
/* Fields derived during parse analysis: */
List *colTypes; /* OID list of output column type OIDs */
List *colTypmods; /* integer list of output column typmods */
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index ede97b7..bf3b6e9 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -47,6 +47,7 @@ typedef struct Value
long ival; /* machine integer */
char *str; /* string */
} val;
+ int location; /* token location, or -1 if unknown */
} Value;
#define intVal(v) (((Value *)(v))->val.ival)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,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/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index ce0c8ce..6b44903 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1597,3 +1597,28 @@ select pg_get_viewdef('tt19v', true);
set client_min_messages = warning;
DROP SCHEMA temp_view_test CASCADE;
DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c661f1d..fdd5a46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2309,6 +2309,23 @@ toyemp| SELECT emp.name,
emp.location,
(12 * emp.salary) AS annualsal
FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+ 2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+ 4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+ 2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+ 4 AS b,
+ 5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+ 2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+ 4 AS a,
+ 5 AS c;
SELECT tablename, rulename, definition FROM pg_rules
ORDER BY tablename, rulename;
pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..6451576 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ 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 UNION CORRESPONDING SELECT 2 two;
+ two
+-----
+ 2
+ 1
+(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(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 (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)
+
+-- 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)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR: there is 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: there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+ ^
+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 (x) SELECT 10 c, 20 d;
+ERROR: there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+ ^
+HINT: UNION queries with a CORRESPONDING clause must have at least one column with the same name
-- Mixed types
SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
two
@@ -124,6 +205,105 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
2
(2 rows)
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+ b
+---
+ 2
+ 4
+ 6
+(3 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR: corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+ ^
+HINT: In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR: corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ ^
+HINT: In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR: corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+ ^
+HINT: In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR: corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ ^
+HINT: In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR: column name "b" is not unique in CORRESPONDING BY clause
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+ ^
+HINT: CORRESPONDING BY clause must contain unique column names only.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+ERROR: there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+ ^
+HINT: UNION queries with a CORRESPONDING clause must have at least one column with the same name
--
-- Try testing from tables...
--
@@ -258,6 +438,74 @@ 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 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 +568,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 +643,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 +744,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 +797,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/create_view.sql b/src/test/regress/sql/create_view.sql
index c27f103..3ea31ba 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -532,3 +532,13 @@ select pg_get_viewdef('tt19v', true);
set client_min_messages = warning;
DROP SCHEMA temp_view_test CASCADE;
DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..9470e1a 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ 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 UNION CORRESPONDING SELECT 2 two;
+
+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(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 (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;
+
+-- 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;
+
+-- 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;
@@ -40,6 +69,30 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+
--
-- Try testing from tables...
--
@@ -90,6 +143,29 @@ 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 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 +188,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 +212,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 +246,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 +275,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