diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 070f8b43d0f..ed180ac91c1 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1,5 +1,5 @@
@@ -22,7 +22,7 @@ Postgres documentation
1999-07-20
-SELECT [ ALL | DISTINCT [ ON column ] ]
+SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
expression [ AS name ] [, ...]
[ INTO [ TEMPORARY | TEMP ] [ TABLE ] new_table ]
[ FROM table [ alias ] [, ...] ]
@@ -201,16 +201,29 @@ SELECT [ ALL | DISTINCT [ ON column
- DISTINCT will eliminate all duplicate rows from the
+ DISTINCT will eliminate duplicate rows from the
result.
- DISTINCT ON column
- will eliminate all duplicates in the specified column; this is
- similar to using
- GROUP BY column.
- ALL will return all candidate rows,
+ ALL (the default) will return all candidate rows,
including duplicates.
+
+ DISTINCT ON eliminates rows that match on all the
+ specified expressions, keeping only the first row of each set of
+ duplicates. Note that "the first row" of each set is unpredictable
+ unless ORDER BY is used to ensure that the desired
+ row appears first. For example,
+
+ SELECT DISTINCT ON (location) location, time, report
+ FROM weatherReports
+ ORDER BY location, time DESC;
+
+ retrieves the most recent weather report for each location. But if
+ we had not used ORDER BY to force descending order of time values
+ for each location, we'd have gotten a report of unpredictable age
+ for each location.
+
+
The GROUP BY clause allows a user to divide a table
conceptually into groups.
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index f600a78fd24..1cbae3519a9 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.35 2000/01/26 05:56:22 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.36 2000/01/27 18:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -753,7 +753,7 @@ NodeGetResultTupleSlot(Plan *node)
{
UniqueState *uniquestate = ((Unique *) node)->uniquestate;
- slot = uniquestate->cs_ResultTupleSlot;
+ slot = uniquestate->cstate.cs_ResultTupleSlot;
}
break;
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 017929424bc..cad023776d8 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -9,12 +9,13 @@
*
* DESCRIPTION
* The Group node is designed for handling queries with a GROUP BY clause.
- * It's outer plan must be a sort node. It assumes that the tuples it gets
- * back from the outer plan is sorted in the order specified by the group
- * columns. (ie. tuples from the same group are consecutive)
+ * Its outer plan must deliver tuples that are sorted in the order
+ * specified by the grouping columns (ie. tuples from the same group are
+ * consecutive). That way, we just have to compare adjacent tuples to
+ * locate group boundaries.
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.32 2000/01/26 05:56:22 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.33 2000/01/27 18:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -23,13 +24,14 @@
#include "access/heapam.h"
#include "access/printtup.h"
+#include "catalog/pg_operator.h"
#include "executor/executor.h"
#include "executor/nodeGroup.h"
+#include "parser/parse_oper.h"
+#include "parser/parse_type.h"
static TupleTableSlot *ExecGroupEveryTuple(Group *node);
static TupleTableSlot *ExecGroupOneTuple(Group *node);
-static bool sameGroup(HeapTuple oldslot, HeapTuple newslot,
- int numCols, AttrNumber *grpColIdx, TupleDesc tupdesc);
/* ---------------------------------------
* ExecGroup -
@@ -38,11 +40,11 @@ static bool sameGroup(HeapTuple oldslot, HeapTuple newslot,
* tuplePerGroup is TRUE, every tuple from the same group will be
* returned, followed by a NULL at the end of each group. This is
* useful for Agg node which needs to aggregate over tuples of the same
- * group. (eg. SELECT salary, count{*} FROM emp GROUP BY salary)
+ * group. (eg. SELECT salary, count(*) FROM emp GROUP BY salary)
*
* If tuplePerGroup is FALSE, only one tuple per group is returned. The
* tuple returned contains only the group columns. NULL is returned only
- * at the end when no more groups is present. This is useful when
+ * at the end when no more groups are present. This is useful when
* the query does not involve aggregates. (eg. SELECT salary FROM emp
* GROUP BY salary)
* ------------------------------------------
@@ -66,6 +68,7 @@ ExecGroupEveryTuple(Group *node)
GroupState *grpstate;
EState *estate;
ExprContext *econtext;
+ TupleDesc tupdesc;
HeapTuple outerTuple = NULL;
HeapTuple firsttuple;
@@ -87,6 +90,8 @@ ExecGroupEveryTuple(Group *node)
econtext = grpstate->csstate.cstate.cs_ExprContext;
+ tupdesc = ExecGetScanType(&grpstate->csstate);
+
/* if we haven't returned first tuple of new group yet ... */
if (grpstate->grp_useFirstTuple)
{
@@ -110,20 +115,25 @@ ExecGroupEveryTuple(Group *node)
outerTuple = outerslot->val;
firsttuple = grpstate->grp_firstTuple;
- /* this should occur on the first call only */
if (firsttuple == NULL)
+ {
+ /* this should occur on the first call only */
grpstate->grp_firstTuple = heap_copytuple(outerTuple);
+ }
else
{
-
/*
* Compare with first tuple and see if this tuple is of the
* same group.
*/
- if (!sameGroup(firsttuple, outerTuple,
- node->numCols, node->grpColIdx,
- ExecGetScanType(&grpstate->csstate)))
+ if (! execTuplesMatch(firsttuple, outerTuple,
+ tupdesc,
+ node->numCols, node->grpColIdx,
+ grpstate->eqfunctions))
{
+ /*
+ * No; save the tuple to return it next time, and return NULL
+ */
grpstate->grp_useFirstTuple = TRUE;
heap_freetuple(firsttuple);
grpstate->grp_firstTuple = heap_copytuple(outerTuple);
@@ -164,6 +174,7 @@ ExecGroupOneTuple(Group *node)
GroupState *grpstate;
EState *estate;
ExprContext *econtext;
+ TupleDesc tupdesc;
HeapTuple outerTuple = NULL;
HeapTuple firsttuple;
@@ -185,10 +196,12 @@ ExecGroupOneTuple(Group *node)
econtext = node->grpstate->csstate.cstate.cs_ExprContext;
+ tupdesc = ExecGetScanType(&grpstate->csstate);
+
firsttuple = grpstate->grp_firstTuple;
- /* this should occur on the first call only */
if (firsttuple == NULL)
{
+ /* this should occur on the first call only */
outerslot = ExecProcNode(outerPlan(node), (Plan *) node);
if (TupIsNull(outerslot))
{
@@ -213,14 +226,14 @@ ExecGroupOneTuple(Group *node)
}
outerTuple = outerslot->val;
- /* ----------------
- * Compare with first tuple and see if this tuple is of
- * the same group.
- * ----------------
+ /*
+ * Compare with first tuple and see if this tuple is of the
+ * same group.
*/
- if ((!sameGroup(firsttuple, outerTuple,
- node->numCols, node->grpColIdx,
- ExecGetScanType(&grpstate->csstate))))
+ if (! execTuplesMatch(firsttuple, outerTuple,
+ tupdesc,
+ node->numCols, node->grpColIdx,
+ grpstate->eqfunctions))
break;
}
@@ -311,6 +324,14 @@ ExecInitGroup(Group *node, EState *estate, Plan *parent)
ExecAssignResultTypeFromTL((Plan *) node, &grpstate->csstate.cstate);
ExecAssignProjectionInfo((Plan *) node, &grpstate->csstate.cstate);
+ /*
+ * Precompute fmgr lookup data for inner loop
+ */
+ grpstate->eqfunctions =
+ execTuplesMatchPrepare(ExecGetScanType(&grpstate->csstate),
+ node->numCols,
+ node->grpColIdx);
+
return TRUE;
}
@@ -347,80 +368,6 @@ ExecEndGroup(Group *node)
}
}
-/*****************************************************************************
- *
- *****************************************************************************/
-
-/*
- * code swiped from nodeUnique.c
- */
-static bool
-sameGroup(HeapTuple oldtuple,
- HeapTuple newtuple,
- int numCols,
- AttrNumber *grpColIdx,
- TupleDesc tupdesc)
-{
- bool isNull1,
- isNull2;
- Datum attr1,
- attr2;
- char *val1,
- *val2;
- int i;
- AttrNumber att;
- Oid typoutput,
- typelem;
-
- for (i = 0; i < numCols; i++)
- {
- att = grpColIdx[i];
- getTypeOutAndElem((Oid) tupdesc->attrs[att - 1]->atttypid,
- &typoutput, &typelem);
-
- attr1 = heap_getattr(oldtuple,
- att,
- tupdesc,
- &isNull1);
-
- attr2 = heap_getattr(newtuple,
- att,
- tupdesc,
- &isNull2);
-
- if (isNull1 == isNull2)
- {
- if (isNull1) /* both are null, they are equal */
- continue;
-
- val1 = fmgr(typoutput, attr1, typelem,
- tupdesc->attrs[att - 1]->atttypmod);
- val2 = fmgr(typoutput, attr2, typelem,
- tupdesc->attrs[att - 1]->atttypmod);
-
- /*
- * now, val1 and val2 are ascii representations so we can use
- * strcmp for comparison
- */
- if (strcmp(val1, val2) != 0)
- {
- pfree(val1);
- pfree(val2);
- return FALSE;
- }
- pfree(val1);
- pfree(val2);
- }
- else
- {
- /* one is null and the other isn't, they aren't equal */
- return FALSE;
- }
- }
-
- return TRUE;
-}
-
void
ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent)
{
@@ -438,3 +385,104 @@ ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent)
((Plan *) node)->lefttree->chgParam == NULL)
ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
}
+
+/*****************************************************************************
+ * Code shared with nodeUnique.c
+ *****************************************************************************/
+
+/*
+ * execTuplesMatch
+ * Return true if two tuples match in all the indicated fields.
+ * This is used to detect group boundaries in nodeGroup, and to
+ * decide whether two tuples are distinct or not in nodeUnique.
+ *
+ * tuple1, tuple2: the tuples to compare
+ * tupdesc: tuple descriptor applying to both tuples
+ * numCols: the number of attributes to be examined
+ * matchColIdx: array of attribute column numbers
+ * eqFunctions: array of fmgr lookup info for the equality functions to use
+ */
+bool
+execTuplesMatch(HeapTuple tuple1,
+ HeapTuple tuple2,
+ TupleDesc tupdesc,
+ int numCols,
+ AttrNumber *matchColIdx,
+ FmgrInfo *eqfunctions)
+{
+ int i;
+
+ /*
+ * We cannot report a match without checking all the fields, but we
+ * can report a non-match as soon as we find unequal fields. So,
+ * start comparing at the last field (least significant sort key).
+ * That's the most likely to be different...
+ */
+ for (i = numCols; --i >= 0; )
+ {
+ AttrNumber att = matchColIdx[i];
+ Datum attr1,
+ attr2;
+ bool isNull1,
+ isNull2;
+ Datum equal;
+
+ attr1 = heap_getattr(tuple1,
+ att,
+ tupdesc,
+ &isNull1);
+
+ attr2 = heap_getattr(tuple2,
+ att,
+ tupdesc,
+ &isNull2);
+
+ if (isNull1 != isNull2)
+ return FALSE; /* one null and one not; they aren't equal */
+
+ if (isNull1)
+ continue; /* both are null, treat as equal */
+
+ /* Apply the type-specific equality function */
+
+ equal = (Datum) (*fmgr_faddr(& eqfunctions[i])) (attr1, attr2);
+
+ if (DatumGetInt32(equal) == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * execTuplesMatchPrepare
+ * Look up the equality functions needed for execTuplesMatch.
+ * The result is a palloc'd array.
+ */
+FmgrInfo *
+execTuplesMatchPrepare(TupleDesc tupdesc,
+ int numCols,
+ AttrNumber *matchColIdx)
+{
+ FmgrInfo *eqfunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
+ int i;
+
+ for (i = 0; i < numCols; i++)
+ {
+ AttrNumber att = matchColIdx[i];
+ Oid typid = tupdesc->attrs[att - 1]->atttypid;
+ Operator eq_operator;
+ Form_pg_operator pgopform;
+
+ eq_operator = oper("=", typid, typid, true);
+ if (!HeapTupleIsValid(eq_operator))
+ {
+ elog(ERROR, "Unable to identify an equality operator for type '%s'",
+ typeidTypeName(typid));
+ }
+ pgopform = (Form_pg_operator) GETSTRUCT(eq_operator);
+ fmgr_info(pgopform->oprcode, & eqfunctions[i]);
+ }
+
+ return eqfunctions;
+}
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 6078e0f68a9..f9f1fe81ab3 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeUnique.c,v 1.26 2000/01/26 05:56:24 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeUnique.c,v 1.27 2000/01/27 18:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -29,79 +29,14 @@
#include "access/heapam.h"
#include "access/printtup.h"
#include "executor/executor.h"
+#include "executor/nodeGroup.h"
#include "executor/nodeUnique.h"
-/* ----------------------------------------------------------------
- * ExecIdenticalTuples
- *
- * This is a hack function used by ExecUnique to see if
- * two tuples are identical. This should be provided
- * by the heap tuple code but isn't. The real problem
- * is that we assume we can byte compare tuples to determine
- * if they are "equal". In fact, if we have user defined
- * types there may be problems because it's possible that
- * an ADT may have multiple representations with the
- * same ADT value. -cim
- * ----------------------------------------------------------------
- */
-static bool /* true if tuples are identical, false
- * otherwise */
-ExecIdenticalTuples(TupleTableSlot *t1, TupleTableSlot *t2)
-{
- HeapTuple h1;
- HeapTuple h2;
- char *d1;
- char *d2;
- int len;
-
- h1 = t1->val;
- h2 = t2->val;
-
- /* ----------------
- * if tuples aren't the same length then they are
- * obviously different (one may have null attributes).
- * ----------------
- */
- if (h1->t_len != h2->t_len)
- return false;
-
- /* ----------------
- * if the tuples have different header offsets then
- * they are different. This will prevent us from returning
- * true when comparing tuples of one attribute where one of
- * two we're looking at is null (t_len - t_hoff == 0).
- * THE t_len FIELDS CAN BE THE SAME IN THIS CASE!!
- * ----------------
- */
- if (h1->t_data->t_hoff != h2->t_data->t_hoff)
- return false;
-
- /* ----------------
- * ok, now get the pointers to the data and the
- * size of the attribute portion of the tuple.
- * ----------------
- */
- d1 = (char *) GETSTRUCT(h1);
- d2 = (char *) GETSTRUCT(h2);
- len = (int) h1->t_len - (int) h1->t_data->t_hoff;
-
- /* ----------------
- * byte compare the data areas and return the result.
- * ----------------
- */
- if (memcmp(d1, d2, len) != 0)
- return false;
-
- return true;
-}
-
/* ----------------------------------------------------------------
* ExecUnique
*
* This is a very simple node which filters out duplicate
* tuples from a stream of sorted tuples from a subplan.
- *
- * XXX see comments below regarding freeing tuples.
* ----------------------------------------------------------------
*/
TupleTableSlot * /* return: a tuple or NULL */
@@ -111,11 +46,7 @@ ExecUnique(Unique *node)
TupleTableSlot *resultTupleSlot;
TupleTableSlot *slot;
Plan *outerPlan;
- char *uniqueAttr;
- AttrNumber uniqueAttrNum;
TupleDesc tupDesc;
- Oid typoutput,
- typelem;
/* ----------------
* get information from the node
@@ -123,22 +54,8 @@ ExecUnique(Unique *node)
*/
uniquestate = node->uniquestate;
outerPlan = outerPlan((Plan *) node);
- resultTupleSlot = uniquestate->cs_ResultTupleSlot;
- uniqueAttr = node->uniqueAttr;
- uniqueAttrNum = node->uniqueAttrNum;
-
- if (uniqueAttr)
- {
- tupDesc = ExecGetResultType(uniquestate);
- getTypeOutAndElem((Oid) tupDesc->attrs[uniqueAttrNum - 1]->atttypid,
- &typoutput, &typelem);
- }
- else
- { /* keep compiler quiet */
- tupDesc = NULL;
- typoutput = InvalidOid;
- typelem = InvalidOid;
- }
+ resultTupleSlot = uniquestate->cstate.cs_ResultTupleSlot;
+ tupDesc = ExecGetResultType(& uniquestate->cstate);
/* ----------------
* now loop, returning only non-duplicate tuples.
@@ -157,83 +74,38 @@ ExecUnique(Unique *node)
return NULL;
/* ----------------
- * we use the result tuple slot to hold our saved tuples.
- * if we haven't a saved tuple to compare our new tuple with,
- * then we exit the loop. This new tuple as the saved tuple
- * the next time we get here.
+ * Always return the first tuple from the subplan.
* ----------------
*/
- if (TupIsNull(resultTupleSlot))
+ if (uniquestate->priorTuple == NULL)
break;
/* ----------------
- * now test if the new tuple and the previous
+ * Else test if the new tuple and the previously returned
* tuple match. If so then we loop back and fetch
* another new tuple from the subplan.
* ----------------
*/
-
- if (uniqueAttr)
- {
-
- /*
- * to check equality, we check to see if the typoutput of the
- * attributes are equal
- */
- bool isNull1,
- isNull2;
- Datum attr1,
- attr2;
- char *val1,
- *val2;
-
- attr1 = heap_getattr(slot->val,
- uniqueAttrNum, tupDesc, &isNull1);
- attr2 = heap_getattr(resultTupleSlot->val,
- uniqueAttrNum, tupDesc, &isNull2);
-
- if (isNull1 == isNull2)
- {
- if (isNull1) /* both are null, they are equal */
- continue;
- val1 = fmgr(typoutput, attr1, typelem,
- tupDesc->attrs[uniqueAttrNum - 1]->atttypmod);
- val2 = fmgr(typoutput, attr2, typelem,
- tupDesc->attrs[uniqueAttrNum - 1]->atttypmod);
-
- /*
- * now, val1 and val2 are ascii representations so we can
- * use strcmp for comparison
- */
- if (strcmp(val1, val2) == 0) /* they are equal */
- {
- pfree(val1);
- pfree(val2);
- continue;
- }
- pfree(val1);
- pfree(val2);
- break;
- }
- else
-/* one is null and the other isn't, they aren't equal */
- break;
-
- }
- else
- {
- if (!ExecIdenticalTuples(slot, resultTupleSlot))
- break;
- }
-
+ if (! execTuplesMatch(slot->val, uniquestate->priorTuple,
+ tupDesc,
+ node->numCols, node->uniqColIdx,
+ uniquestate->eqfunctions))
+ break;
}
/* ----------------
- * we have a new tuple different from the previous saved tuple
- * so we save it in the saved tuple slot. We copy the tuple
- * so we don't increment the buffer ref count.
+ * We have a new tuple different from the previous saved tuple (if any).
+ * Save it and return it. Note that we make two copies of the tuple:
+ * one to keep for our own future comparisons, and one to return to the
+ * caller. We need to copy the tuple returned by the subplan to avoid
+ * holding buffer refcounts, and we need our own copy because the caller
+ * may alter the resultTupleSlot (eg via ExecRemoveJunk).
* ----------------
*/
+ if (uniquestate->priorTuple != NULL)
+ heap_freetuple(uniquestate->priorTuple);
+ uniquestate->priorTuple = heap_copytuple(slot->val);
+
ExecStoreTuple(heap_copytuple(slot->val),
resultTupleSlot,
InvalidBuffer,
@@ -254,7 +126,6 @@ ExecInitUnique(Unique *node, EState *estate, Plan *parent)
{
UniqueState *uniquestate;
Plan *outerPlan;
- char *uniqueAttr;
/* ----------------
* assign execution state to node
@@ -268,10 +139,10 @@ ExecInitUnique(Unique *node, EState *estate, Plan *parent)
*/
uniquestate = makeNode(UniqueState);
node->uniquestate = uniquestate;
- uniqueAttr = node->uniqueAttr;
+ uniquestate->priorTuple = NULL;
/* ----------------
- * Miscellanious initialization
+ * Miscellaneous initialization
*
* + assign node's base_id
* + assign debugging hooks and
@@ -280,14 +151,14 @@ ExecInitUnique(Unique *node, EState *estate, Plan *parent)
* they never call ExecQual or ExecTargetList.
* ----------------
*/
- ExecAssignNodeBaseInfo(estate, uniquestate, parent);
+ ExecAssignNodeBaseInfo(estate, & uniquestate->cstate, parent);
#define UNIQUE_NSLOTS 1
/* ------------
* Tuple table initialization
* ------------
*/
- ExecInitResultTupleSlot(estate, uniquestate);
+ ExecInitResultTupleSlot(estate, & uniquestate->cstate);
/* ----------------
* then initialize outer plan
@@ -301,31 +172,17 @@ ExecInitUnique(Unique *node, EState *estate, Plan *parent)
* projection info for this node appropriately
* ----------------
*/
- ExecAssignResultTypeFromOuterPlan((Plan *) node, uniquestate);
- uniquestate->cs_ProjInfo = NULL;
+ ExecAssignResultTypeFromOuterPlan((Plan *) node, & uniquestate->cstate);
+ uniquestate->cstate.cs_ProjInfo = NULL;
- if (uniqueAttr)
- {
- TupleDesc tupDesc;
- int i = 0;
-
- tupDesc = ExecGetResultType(uniquestate);
-
- /*
- * the parser should have ensured that uniqueAttr is a legal
- * attribute name
- */
- while (strcmp(NameStr(tupDesc->attrs[i]->attname), uniqueAttr) != 0)
- i++;
- node->uniqueAttrNum = i + 1; /* attribute numbers start from 1 */
- }
- else
- node->uniqueAttrNum = InvalidAttrNumber;
-
- /* ----------------
- * all done.
- * ----------------
+ /*
+ * Precompute fmgr lookup data for inner loop
*/
+ uniquestate->eqfunctions =
+ execTuplesMatchPrepare(ExecGetResultType(& uniquestate->cstate),
+ node->numCols,
+ node->uniqColIdx);
+
return TRUE;
}
@@ -347,11 +204,17 @@ ExecCountSlotsUnique(Unique *node)
void
ExecEndUnique(Unique *node)
{
- UniqueState *uniquestate;
+ UniqueState *uniquestate = node->uniquestate;
- uniquestate = node->uniquestate;
ExecEndNode(outerPlan((Plan *) node), (Plan *) node);
- ExecClearTuple(uniquestate->cs_ResultTupleSlot);
+
+ /* clean up tuple table */
+ ExecClearTuple(uniquestate->cstate.cs_ResultTupleSlot);
+ if (uniquestate->priorTuple != NULL)
+ {
+ heap_freetuple(uniquestate->priorTuple);
+ uniquestate->priorTuple = NULL;
+ }
}
@@ -360,7 +223,12 @@ ExecReScanUnique(Unique *node, ExprContext *exprCtxt, Plan *parent)
{
UniqueState *uniquestate = node->uniquestate;
- ExecClearTuple(uniquestate->cs_ResultTupleSlot);
+ ExecClearTuple(uniquestate->cstate.cs_ResultTupleSlot);
+ if (uniquestate->priorTuple != NULL)
+ {
+ heap_freetuple(uniquestate->priorTuple);
+ uniquestate->priorTuple = NULL;
+ }
/*
* if chgParam of subnode is not null then plan will be re-scanned by
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6c8d92355a0..cb447b63717 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.102 2000/01/26 05:56:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.103 2000/01/27 18:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -532,11 +532,9 @@ _copyUnique(Unique *from)
* copy remainder of node
* ----------------
*/
- if (from->uniqueAttr)
- newnode->uniqueAttr = pstrdup(from->uniqueAttr);
- else
- newnode->uniqueAttr = NULL;
- newnode->uniqueAttrNum = from->uniqueAttrNum;
+ newnode->numCols = from->numCols;
+ newnode->uniqColIdx = palloc(from->numCols * sizeof(AttrNumber));
+ memcpy(newnode->uniqColIdx, from->uniqColIdx, from->numCols * sizeof(AttrNumber));
return newnode;
}
@@ -1427,8 +1425,7 @@ _copyQuery(Query *from)
Node_Copy(from, newnode, qual);
Node_Copy(from, newnode, rowMark);
- if (from->uniqueFlag)
- newnode->uniqueFlag = pstrdup(from->uniqueFlag);
+ Node_Copy(from, newnode, distinctClause);
Node_Copy(from, newnode, sortClause);
Node_Copy(from, newnode, groupClause);
Node_Copy(from, newnode, havingQual);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 888f21543cc..b4351462c08 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.56 2000/01/26 05:56:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.57 2000/01/27 18:11:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -593,16 +593,8 @@ _equalQuery(Query *a, Query *b)
return false;
if (!equal(a->rowMark, b->rowMark))
return false;
- if (a->uniqueFlag && b->uniqueFlag)
- {
- if (strcmp(a->uniqueFlag, b->uniqueFlag) != 0)
- return false;
- }
- else
- {
- if (a->uniqueFlag != b->uniqueFlag)
- return false;
- }
+ if (!equal(a->distinctClause, b->distinctClause))
+ return false;
if (!equal(a->sortClause, b->sortClause))
return false;
if (!equal(a->groupClause, b->groupClause))
diff --git a/src/backend/nodes/freefuncs.c b/src/backend/nodes/freefuncs.c
index cfaa119937e..fc5c3506d8c 100644
--- a/src/backend/nodes/freefuncs.c
+++ b/src/backend/nodes/freefuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/Attic/freefuncs.c,v 1.32 2000/01/26 05:56:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/Attic/freefuncs.c,v 1.33 2000/01/27 18:11:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -427,8 +427,7 @@ _freeUnique(Unique *node)
* free remainder of node
* ----------------
*/
- if (node->uniqueAttr)
- pfree(node->uniqueAttr);
+ pfree(node->uniqColIdx);
pfree(node);
}
@@ -1072,9 +1071,7 @@ _freeQuery(Query *node)
freeObject(node->targetList);
freeObject(node->qual);
freeObject(node->rowMark);
- if (node->uniqueFlag)
- pfree(node->uniqueFlag);
-
+ freeObject(node->distinctClause);
freeObject(node->sortClause);
freeObject(node->groupClause);
freeObject(node->havingQual);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7c7b7760a11..4475fa382bb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.104 2000/01/26 05:56:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.105 2000/01/27 18:11:28 tgl Exp $
*
* NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which
@@ -256,12 +256,13 @@ _outQuery(StringInfo str, Query *node)
_outToken(str, node->into);
appendStringInfo(str,
- " :isPortal %s :isBinary %s :isTemp %s :unionall %s :unique ",
+ " :isPortal %s :isBinary %s :isTemp %s :unionall %s :distinctClause ",
node->isPortal ? "true" : "false",
node->isBinary ? "true" : "false",
node->isTemp ? "true" : "false",
node->unionall ? "true" : "false");
- _outToken(str, node->uniqueFlag);
+ _outNode(str, node->distinctClause);
+
appendStringInfo(str, " :sortClause ");
_outNode(str, node->sortClause);
@@ -584,9 +585,10 @@ _outUnique(StringInfo str, Unique *node)
appendStringInfo(str, " UNIQUE ");
_outPlanInfo(str, (Plan *) node);
- appendStringInfo(str, " :nonameid %u :keycount %d ",
+ appendStringInfo(str, " :nonameid %u :keycount %d :numCols %d ",
node->nonameid,
- node->keycount);
+ node->keycount,
+ node->numCols);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index cf63506a05f..9dccbf50170 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.80 2000/01/26 05:56:32 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.81 2000/01/27 18:11:28 tgl Exp $
*
* NOTES
* Most of the read functions for plan nodes are tested. (In fact, they
@@ -111,12 +111,8 @@ _readQuery()
token = lsptok(NULL, &length); /* get unionall */
local_node->unionall = (token[0] == 't') ? true : false;
- token = lsptok(NULL, &length); /* skip :uniqueFlag */
- token = lsptok(NULL, &length); /* get uniqueFlag */
- if (length == 0)
- local_node->uniqueFlag = NULL;
- else
- local_node->uniqueFlag = debackslash(token, length);
+ token = lsptok(NULL, &length); /* skip :distinctClause */
+ local_node->distinctClause = nodeRead(true);
token = lsptok(NULL, &length); /* skip :sortClause */
local_node->sortClause = nodeRead(true);
@@ -624,33 +620,6 @@ _readAgg()
return local_node;
}
-/* ----------------
- * _readUnique
- *
- * For some reason, unique is a subclass of Noname.
- */
-static Unique *
-_readUnique()
-{
- Unique *local_node;
- char *token;
- int length;
-
- local_node = makeNode(Unique);
-
- _getPlan((Plan *) local_node);
-
- token = lsptok(NULL, &length); /* eat :nonameid */
- token = lsptok(NULL, &length); /* get :nonameid */
- local_node->nonameid = atol(token);
-
- token = lsptok(NULL, &length); /* eat :keycount */
- token = lsptok(NULL, &length); /* get :keycount */
- local_node->keycount = atoi(token);
-
- return local_node;
-}
-
/* ----------------
* _readHash
*
@@ -1847,8 +1816,6 @@ parsePlanString(void)
return_value = _readSubLink();
else if (length == 3 && strncmp(token, "AGG", length) == 0)
return_value = _readAgg();
- else if (length == 6 && strncmp(token, "UNIQUE", length) == 0)
- return_value = _readUnique();
else if (length == 4 && strncmp(token, "HASH", length) == 0)
return_value = _readHash();
else if (length == 6 && strncmp(token, "RESDOM", length) == 0)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab69742482a..70a73821641 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.81 2000/01/26 05:56:37 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.82 2000/01/27 18:11:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1342,16 +1342,19 @@ make_group(List *tlist,
}
/*
- * The uniqueAttr argument must be a null-terminated string,
- * either the name of the attribute to select unique on
- * or "*"
+ * distinctList is a list of SortClauses, identifying the targetlist items
+ * that should be considered by the Unique filter.
*/
Unique *
-make_unique(List *tlist, Plan *lefttree, char *uniqueAttr)
+make_unique(List *tlist, Plan *lefttree, List *distinctList)
{
Unique *node = makeNode(Unique);
Plan *plan = &node->plan;
+ int numCols = length(distinctList);
+ int keyno = 0;
+ AttrNumber *uniqColIdx;
+ List *slitem;
copy_plan_costsize(plan, lefttree);
plan->state = (EState *) NULL;
@@ -1361,10 +1364,22 @@ make_unique(List *tlist, Plan *lefttree, char *uniqueAttr)
plan->righttree = NULL;
node->nonameid = _NONAME_RELATION_ID_;
node->keycount = 0;
- if (strcmp(uniqueAttr, "*") == 0)
- node->uniqueAttr = NULL;
- else
- node->uniqueAttr = pstrdup(uniqueAttr);
+
+ /* convert SortClause list into array of attr indexes, as wanted by exec */
+ Assert(numCols > 0);
+ uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
+
+ foreach(slitem, distinctList)
+ {
+ SortClause *sortcl = (SortClause *) lfirst(slitem);
+ TargetEntry *tle = get_sortgroupclause_tle(sortcl, tlist);
+
+ uniqColIdx[keyno++] = tle->resdom->resno;
+ }
+
+ node->numCols = numCols;
+ node->uniqColIdx = uniqColIdx;
+
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0b711f32099..28483fd4734 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.73 2000/01/26 05:56:37 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.74 2000/01/27 18:11:31 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -381,12 +381,12 @@ union_planner(Query *parse)
}
/*
- * Finally, if there is a UNIQUE clause, add the UNIQUE node.
+ * Finally, if there is a DISTINCT clause, add the UNIQUE node.
*/
- if (parse->uniqueFlag)
+ if (parse->distinctClause)
{
result_plan = (Plan *) make_unique(tlist, result_plan,
- parse->uniqueFlag);
+ parse->distinctClause);
}
return result_plan;
@@ -583,20 +583,8 @@ make_sortplan(List *tlist, List *sortcls, Plan *plannode)
foreach(i, sortcls)
{
SortClause *sortcl = (SortClause *) lfirst(i);
- Index refnumber = sortcl->tleSortGroupRef;
- TargetEntry *tle = NULL;
- Resdom *resdom;
- List *l;
-
- foreach(l, temp_tlist)
- {
- tle = (TargetEntry *) lfirst(l);
- if (tle->resdom->ressortgroupref == refnumber)
- break;
- }
- if (l == NIL)
- elog(ERROR, "make_sortplan: ORDER BY expression not found in targetlist");
- resdom = tle->resdom;
+ TargetEntry *tle = get_sortgroupclause_tle(sortcl, temp_tlist);
+ Resdom *resdom = tle->resdom;
/*
* Check for the possibility of duplicate order-by clauses --- the
diff --git a/src/backend/optimizer/prep/prepkeyset.c b/src/backend/optimizer/prep/prepkeyset.c
index 127505597a4..fc192e6f28b 100644
--- a/src/backend/optimizer/prep/prepkeyset.c
+++ b/src/backend/optimizer/prep/prepkeyset.c
@@ -104,9 +104,7 @@ transformKeySetQuery(Query *origNode)
unionNode->isPortal = origNode->isPortal;
unionNode->isBinary = origNode->isBinary;
- if (origNode->uniqueFlag)
- unionNode->uniqueFlag = pstrdup(origNode->uniqueFlag);
-
+ Node_Copy(origNode, unionNode, distinctClause);
Node_Copy(origNode, unionNode, sortClause);
Node_Copy(origNode, unionNode, rtable);
Node_Copy(origNode, unionNode, targetList);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index a27305ef3e8..4323b652e8d 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.41 2000/01/26 05:56:39 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.42 2000/01/27 18:11:32 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,6 +21,7 @@
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/prep.h"
+#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
@@ -131,7 +132,7 @@ plan_union_queries(Query *parse)
!last_union_all_flag)
{
parse->sortClause = NIL;
- parse->uniqueFlag = NULL;
+ parse->distinctClause = NIL;
}
parse->unionClause = NIL; /* prevent recursion */
@@ -183,17 +184,28 @@ plan_union_queries(Query *parse)
if (!last_union_all_flag)
{
/* Need SELECT DISTINCT behavior to implement UNION.
- * Set uniqueFlag properly, put back the held sortClause,
- * and add any missing columns to the sort clause.
+ * Put back the held sortClause, add any missing columns to the
+ * sort clause, and set distinctClause properly.
*/
- parse->uniqueFlag = "*";
+ List *slitem;
+
parse->sortClause = addAllTargetsToSortList(hold_sortClause,
parse->targetList);
+ parse->distinctClause = NIL;
+ foreach(slitem, parse->sortClause)
+ {
+ SortClause *scl = (SortClause *) lfirst(slitem);
+ TargetEntry *tle = get_sortgroupclause_tle(scl, parse->targetList);
+
+ if (! tle->resdom->resjunk)
+ parse->distinctClause = lappend(parse->distinctClause,
+ copyObject(scl));
+ }
}
else
{
- /* needed so we don't take the flag from the first query */
- parse->uniqueFlag = NULL;
+ /* needed so we don't take SELECT DISTINCT from the first query */
+ parse->distinctClause = NIL;
}
/* Make sure we don't try to apply the first query's grouping stuff
@@ -314,9 +326,9 @@ plan_inherit_query(Relids relids,
* Clear the sorting and grouping qualifications in the subquery,
* so that sorting will only be done once after append
*/
- new_root->uniqueFlag = NULL;
- new_root->sortClause = NULL;
- new_root->groupClause = NULL;
+ new_root->distinctClause = NIL;
+ new_root->sortClause = NIL;
+ new_root->groupClause = NIL;
new_root->havingQual = NULL;
new_root->hasAggs = false; /* shouldn't be any left ... */
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 8edf44190a4..b4c745b25f3 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/tlist.c,v 1.42 2000/01/26 05:56:40 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/tlist.c,v 1.43 2000/01/27 18:11:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -216,6 +216,33 @@ get_expr(TargetEntry *tle)
return (Var *) tle->expr;
}
+/*
+ * get_sortgroupclause_tle
+ * Find the targetlist entry matching the given SortClause
+ * (or GroupClause) by ressortgroupref, and return it.
+ *
+ * Because GroupClause is typedef'd as SortClause, either kind of
+ * node can be passed without casting.
+ */
+TargetEntry *
+get_sortgroupclause_tle(SortClause *sortClause,
+ List *targetList)
+{
+ Index refnumber = sortClause->tleSortGroupRef;
+ List *l;
+
+ foreach(l, targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resdom->ressortgroupref == refnumber)
+ return tle;
+ }
+
+ elog(ERROR, "get_sortgroupclause_tle: ORDER/GROUP BY expression not found in targetlist");
+ return NULL; /* keep compiler quiet */
+}
+
/*
* get_sortgroupclause_expr
* Find the targetlist entry matching the given SortClause
@@ -227,17 +254,7 @@ get_expr(TargetEntry *tle)
Node *
get_sortgroupclause_expr(SortClause *sortClause, List *targetList)
{
- Index refnumber = sortClause->tleSortGroupRef;
- List *l;
+ TargetEntry *tle = get_sortgroupclause_tle(sortClause, targetList);
- foreach(l, targetList)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resdom->ressortgroupref == refnumber)
- return tle->expr;
- }
-
- elog(ERROR, "get_sortgroupclause_expr: ORDER/GROUP BY expression not found in targetlist");
- return NULL; /* keep compiler quiet */
+ return tle->expr;
}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 48178bcb215..fd3dda8f17e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: analyze.c,v 1.133 2000/01/26 05:56:41 momjian Exp $
+ * $Id: analyze.c,v 1.134 2000/01/27 18:11:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -247,7 +247,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
makeRangeTable(pstate, NULL, NULL);
setTargetTable(pstate, stmt->relname);
- qry->uniqueFlag = NULL;
+ qry->distinctClause = NIL;
/* fix where clause */
qry->qual = transformWhereClause(pstate, stmt->whereClause, NULL);
@@ -296,8 +296,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* set up a range table --- note INSERT target is not in it yet */
makeRangeTable(pstate, stmt->fromClause, &fromQual);
- qry->uniqueFlag = stmt->unique;
-
qry->targetList = transformTargetList(pstate, stmt->targetList);
qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual);
@@ -311,13 +309,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
stmt->groupClause,
qry->targetList);
- /* An InsertStmt has no sortClause, but we still call
- * transformSortClause because it also handles uniqueFlag.
- */
- qry->sortClause = transformSortClause(pstate,
- NIL,
- qry->targetList,
- qry->uniqueFlag);
+ /* An InsertStmt has no sortClause */
+ qry->sortClause = NIL;
+
+ qry->distinctClause = transformDistinctClause(pstate,
+ stmt->distinctClause,
+ qry->targetList,
+ & qry->sortClause);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
@@ -1312,8 +1310,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
/* set up a range table */
makeRangeTable(pstate, stmt->fromClause, &fromQual);
- qry->uniqueFlag = stmt->unique;
-
qry->into = stmt->into;
qry->isTemp = stmt->istemp;
qry->isPortal = FALSE;
@@ -1333,8 +1329,12 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->sortClause = transformSortClause(pstate,
stmt->sortClause,
- qry->targetList,
- qry->uniqueFlag);
+ qry->targetList);
+
+ qry->distinctClause = transformDistinctClause(pstate,
+ stmt->distinctClause,
+ qry->targetList,
+ & qry->sortClause);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
@@ -1558,9 +1558,9 @@ CheckSelectForUpdate(Query *qry)
{
if (qry->unionClause != NULL)
elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT clause");
- if (qry->uniqueFlag != NULL)
+ if (qry->distinctClause != NIL)
elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
- if (qry->groupClause != NULL)
+ if (qry->groupClause != NIL)
elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause");
if (qry->hasAggs)
elog(ERROR, "SELECT FOR UPDATE is not allowed with AGGREGATE");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 924d26d26fb..228cb73f3af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.135 2000/01/26 05:56:41 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.136 2000/01/27 18:11:35 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -159,7 +159,7 @@ static Node *doNegate(Node *n);
class, index_name, name, func_name, file_name, aggr_argtype
%type opt_id,
- all_Op, MathOp, opt_name, opt_unique,
+ all_Op, MathOp, opt_name,
OptUseOp, opt_class, SpecialRuleRelation
%type opt_level, opt_encoding
@@ -168,7 +168,7 @@ static Node *doNegate(Node *n);
%type stmtblock, stmtmulti,
result, relation_name_list, OptTableElementList,
- OptInherit, definition,
+ OptInherit, definition, opt_distinct,
opt_with, func_args, func_args_list, func_as,
oper_argtypes, RuleActionList, RuleActionMulti,
opt_column_list, columnList, opt_va_list, va_list,
@@ -2843,7 +2843,7 @@ insert_rest: VALUES '(' target_list ')'
{
$$ = makeNode(InsertStmt);
$$->cols = NULL;
- $$->unique = NULL;
+ $$->distinctClause = NIL;
$$->targetList = $3;
$$->fromClause = NIL;
$$->whereClause = NULL;
@@ -2854,7 +2854,7 @@ insert_rest: VALUES '(' target_list ')'
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
- $$->unique = NULL;
+ $$->distinctClause = NIL;
$$->targetList = NIL;
$$->fromClause = NIL;
$$->whereClause = NULL;
@@ -2873,7 +2873,7 @@ insert_rest: VALUES '(' target_list ')'
elog(ERROR, "INSERT ... SELECT can't have ORDER BY");
$$ = makeNode(InsertStmt);
$$->cols = NIL;
- $$->unique = n->unique;
+ $$->distinctClause = n->distinctClause;
$$->targetList = n->targetList;
$$->fromClause = n->fromClause;
$$->whereClause = n->whereClause;
@@ -2888,7 +2888,7 @@ insert_rest: VALUES '(' target_list ')'
{
$$ = makeNode(InsertStmt);
$$->cols = $2;
- $$->unique = NULL;
+ $$->distinctClause = NIL;
$$->targetList = $6;
$$->fromClause = NIL;
$$->whereClause = NULL;
@@ -2904,7 +2904,7 @@ insert_rest: VALUES '(' target_list ')'
elog(ERROR, "INSERT ... SELECT can't have ORDER BY");
$$ = makeNode(InsertStmt);
$$->cols = $2;
- $$->unique = n->unique;
+ $$->distinctClause = n->distinctClause;
$$->targetList = n->targetList;
$$->fromClause = n->fromClause;
$$->whereClause = n->whereClause;
@@ -3189,12 +3189,12 @@ select_clause: '(' select_clause ')'
}
;
-SubSelect: SELECT opt_unique target_list
+SubSelect: SELECT opt_distinct target_list
result from_clause where_clause
group_clause having_clause
{
SelectStmt *n = makeNode(SelectStmt);
- n->unique = $2;
+ n->distinctClause = $2;
n->unionall = FALSE;
n->targetList = $3;
/* This is new: Subselects support the INTO clause
@@ -3230,10 +3230,13 @@ opt_all: ALL { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
-opt_unique: DISTINCT { $$ = "*"; }
- | DISTINCT ON ColId { $$ = $3; }
- | ALL { $$ = NULL; }
- | /*EMPTY*/ { $$ = NULL; }
+/* We use (NIL) as a placeholder to indicate that all target expressions
+ * should be placed in the DISTINCT list during parsetree analysis.
+ */
+opt_distinct: DISTINCT { $$ = lcons(NIL,NIL); }
+ | DISTINCT ON '(' expr_list ')' { $$ = $4; }
+ | ALL { $$ = NIL; }
+ | /*EMPTY*/ { $$ = NIL; }
;
sort_clause: ORDER BY sortby_list { $$ = $3; }
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ba2b5f8499b..b22691fa3c2 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.50 2000/01/26 05:56:42 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.51 2000/01/27 18:11:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,12 +28,12 @@
#define ORDER_CLAUSE 0
#define GROUP_CLAUSE 1
+#define DISTINCT_ON_CLAUSE 2
-static char *clauseText[] = {"ORDER", "GROUP"};
+static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
- List *tlist, int clause,
- char *uniqFlag);
+ List *tlist, int clause);
static void parseFromClause(ParseState *pstate, List *frmList, Node **qual);
static char *transformTableEntry(ParseState *pstate, RangeVar *r);
static List *addTargetToSortList(TargetEntry *tle, List *sortlist,
@@ -359,14 +359,13 @@ parseFromClause(ParseState *pstate, List *frmList, Node **qual)
* If no matching entry exists, one is created and appended to the target
* list as a "resjunk" node.
*
- * node the ORDER BY or GROUP BY expression to be matched
+ * node the ORDER BY, GROUP BY, or DISTINCT ON expression to be matched
* tlist the existing target list (NB: this cannot be NIL, which is a
* good thing since we'd be unable to append to it...)
* clause identifies clause type for error messages.
*/
static TargetEntry *
-findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause,
- char *uniqueFlag)
+findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause)
{
TargetEntry *target_result = NULL;
List *tl;
@@ -407,7 +406,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause,
if (target_result != NULL)
{
if (! equal(target_result->expr, tle->expr))
- elog(ERROR, "%s BY '%s' is ambiguous",
+ elog(ERROR, "%s '%s' is ambiguous",
clauseText[clause], name);
}
else
@@ -424,8 +423,8 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause,
int targetlist_pos = 0;
int target_pos;
- if (nodeTag(val) != T_Integer)
- elog(ERROR, "Non-integer constant in %s BY", clauseText[clause]);
+ if (! IsA(val, Integer))
+ elog(ERROR, "Non-integer constant in %s", clauseText[clause]);
target_pos = intVal(val);
foreach(tl, tlist)
{
@@ -438,7 +437,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause,
return tle; /* return the unique match */
}
}
- elog(ERROR, "%s BY position %d is not in target list",
+ elog(ERROR, "%s position %d is not in target list",
clauseText[clause], target_pos);
}
@@ -462,13 +461,9 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause,
/*
* If no matches, construct a new target entry which is appended to
- * the end of the target list. This target is set to be resjunk =
- * TRUE so that it will not be projected into the final tuple.
+ * the end of the target list. This target is given resjunk = TRUE
+ * so that it will not be projected into the final tuple.
*/
- if(clause == ORDER_CLAUSE && uniqueFlag) {
- elog(ERROR, "ORDER BY columns must appear in SELECT DISTINCT target list");
- }
-
target_result = transformTargetEntry(pstate, node, expr, NULL, true);
lappend(tlist, target_result);
@@ -492,7 +487,7 @@ transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist)
TargetEntry *tle;
tle = findTargetlistEntry(pstate, lfirst(gl),
- targetlist, GROUP_CLAUSE, NULL);
+ targetlist, GROUP_CLAUSE);
/* avoid making duplicate grouplist entries */
if (! exprIsInSortList(tle->expr, glist, targetlist))
@@ -514,74 +509,149 @@ transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist)
/*
* transformSortClause -
- * transform an Order By clause
- *
+ * transform an ORDER BY clause
*/
List *
transformSortClause(ParseState *pstate,
List *orderlist,
- List *targetlist,
- char *uniqueFlag)
+ List *targetlist)
{
List *sortlist = NIL;
List *olitem;
- /* Transform all the explicit ORDER BY clauses */
-
foreach(olitem, orderlist)
{
SortGroupBy *sortby = lfirst(olitem);
TargetEntry *tle;
tle = findTargetlistEntry(pstate, sortby->node,
- targetlist, ORDER_CLAUSE, uniqueFlag);
+ targetlist, ORDER_CLAUSE);
sortlist = addTargetToSortList(tle, sortlist, targetlist,
sortby->useOp);
}
- /* If we have a DISTINCT clause, add any necessary entries to
- * the sortlist to ensure that all the DISTINCT columns will be
- * sorted. A subsequent UNIQUE pass will then do the right thing.
- */
-
- if (uniqueFlag)
- {
- if (uniqueFlag[0] == '*')
- {
- /*
- * concatenate all elements from target list that are not
- * already in the sortby list
- */
- sortlist = addAllTargetsToSortList(sortlist, targetlist);
- }
- else
- {
- TargetEntry *tle = NULL;
- char *uniqueAttrName = uniqueFlag;
- List *i;
-
- /* only create sort clause with the specified unique attribute */
- foreach(i, targetlist)
- {
- tle = (TargetEntry *) lfirst(i);
- if (strcmp(tle->resdom->resname, uniqueAttrName) == 0)
- break;
- }
- if (i == NIL)
- elog(ERROR, "All fields in the UNIQUE ON clause must appear in the target list");
-
- sortlist = addTargetToSortList(tle, sortlist, targetlist, NULL);
- }
- }
-
return sortlist;
}
+/*
+ * transformDistinctClause -
+ * transform a DISTINCT or DISTINCT ON clause
+ *
+ * Since we may need to add items to the query's sortClause list, that list
+ * is passed by reference. We might also need to add items to the query's
+ * targetlist, but we assume that cannot be empty initially, so we can
+ * lappend to it even though the pointer is passed by value.
+ */
+List *
+transformDistinctClause(ParseState *pstate, List *distinctlist,
+ List *targetlist, List **sortClause)
+{
+ List *result = NIL;
+ List *slitem;
+ List *dlitem;
+
+ /* No work if there was no DISTINCT clause */
+ if (distinctlist == NIL)
+ return NIL;
+
+ if (lfirst(distinctlist) == NIL)
+ {
+ /* We had SELECT DISTINCT */
+
+ /*
+ * All non-resjunk elements from target list that are not already
+ * in the sort list should be added to it. (We don't really care
+ * what order the DISTINCT fields are checked in, so we can leave
+ * the user's ORDER BY spec alone, and just add additional sort keys
+ * to it to ensure that all targetlist items get sorted.)
+ */
+ *sortClause = addAllTargetsToSortList(*sortClause, targetlist);
+
+ /*
+ * Now, DISTINCT list consists of all non-resjunk sortlist items.
+ * Actually, all the sortlist items had better be non-resjunk!
+ * Otherwise, user wrote SELECT DISTINCT with an ORDER BY item
+ * that does not appear anywhere in the SELECT targetlist, and
+ * we can't implement that with only one sorting pass...
+ */
+ foreach(slitem, *sortClause)
+ {
+ SortClause *scl = (SortClause *) lfirst(slitem);
+ TargetEntry *tle = get_sortgroupclause_tle(scl, targetlist);
+
+ if (tle->resdom->resjunk)
+ elog(ERROR, "For SELECT DISTINCT, ORDER BY expressions must appear in target list");
+ else
+ result = lappend(result, copyObject(scl));
+ }
+ }
+ else
+ {
+ /* We had SELECT DISTINCT ON (expr, ...) */
+
+ /*
+ * If the user writes both DISTINCT ON and ORDER BY, then the two
+ * expression lists must match (until one or the other runs out).
+ * Otherwise the ORDER BY requires a different sort order than the
+ * DISTINCT does, and we can't implement that with only one sort pass
+ * (and if we do two passes, the results will be rather unpredictable).
+ * However, it's OK to have more DISTINCT ON expressions than ORDER BY
+ * expressions; we can just add the extra DISTINCT values to the sort
+ * list, much as we did above for ordinary DISTINCT fields.
+ *
+ * Actually, it'd be OK for the common prefixes of the two lists to
+ * match in any order, but implementing that check seems like more
+ * trouble than it's worth.
+ */
+ List *nextsortlist = *sortClause;
+
+ foreach(dlitem, distinctlist)
+ {
+ TargetEntry *tle;
+
+ tle = findTargetlistEntry(pstate, lfirst(dlitem),
+ targetlist, DISTINCT_ON_CLAUSE);
+
+ if (nextsortlist != NIL)
+ {
+ SortClause *scl = (SortClause *) lfirst(nextsortlist);
+
+ if (tle->resdom->ressortgroupref != scl->tleSortGroupRef)
+ elog(ERROR, "SELECT DISTINCT ON expressions must match initial ORDER BY expressions");
+ result = lappend(result, copyObject(scl));
+ nextsortlist = lnext(nextsortlist);
+ }
+ else
+ {
+ *sortClause = addTargetToSortList(tle, *sortClause,
+ targetlist, NULL);
+ /* Probably, the tle should always have been added at the
+ * end of the sort list ... but search to be safe.
+ */
+ foreach(slitem, *sortClause)
+ {
+ SortClause *scl = (SortClause *) lfirst(slitem);
+
+ if (tle->resdom->ressortgroupref == scl->tleSortGroupRef)
+ {
+ result = lappend(result, copyObject(scl));
+ break;
+ }
+ }
+ if (slitem == NIL)
+ elog(ERROR, "transformDistinctClause: failed to add DISTINCT ON clause to target list");
+ }
+ }
+ }
+
+ return result;
+}
+
/*
* addAllTargetsToSortList
- * Make sure all targets in the targetlist are in the ORDER BY list,
- * adding the not-yet-sorted ones to the end of the list.
+ * Make sure all non-resjunk targets in the targetlist are in the
+ * ORDER BY list, adding the not-yet-sorted ones to the end of the list.
* This is typically used to help implement SELECT DISTINCT.
*
* Returns the updated ORDER BY list.
@@ -595,7 +665,8 @@ addAllTargetsToSortList(List *sortlist, List *targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(i);
- sortlist = addTargetToSortList(tle, sortlist, targetlist, NULL);
+ if (! tle->resdom->resjunk)
+ sortlist = addTargetToSortList(tle, sortlist, targetlist, NULL);
}
return sortlist;
}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index b0ca5f522e0..a3ce8d2e8cb 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.41 2000/01/26 05:56:49 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.42 2000/01/27 18:11:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -312,7 +312,7 @@ DefineQueryRewrite(RuleStmt *stmt)
/*
* DISTINCT on view is not supported
*/
- if (query->uniqueFlag != NULL)
+ if (query->distinctClause != NIL)
elog(ERROR, "DISTINCT not supported in views");
/*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 358dba0a3ec..57169d9336e 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.66 2000/01/26 05:56:49 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.67 2000/01/27 18:11:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -536,8 +536,8 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
subquery->isBinary = FALSE;
subquery->isTemp = FALSE;
subquery->unionall = FALSE;
- subquery->uniqueFlag = NULL;
- subquery->sortClause = NULL;
+ subquery->distinctClause = NIL;
+ subquery->sortClause = NIL;
subquery->rtable = lcons(copyObject(rte), NIL);
subquery->targetList = lcons(tle, NIL);
subquery->qual = modifyAggrefDropQual((Node *) parsetree->qual,
@@ -1725,7 +1725,7 @@ check_targetlists_are_compatible(List *prev_target, List *current_target)
* The operator tree is attached to 'intersectClause' (see rule
* 'SelectStmt' in gram.y) of the 'parsetree' given as an
* argument. First we remember some clauses (the sortClause, the
- * unique flag etc.) Then we translate the operator tree to DNF
+ * distinctClause etc.) Then we translate the operator tree to DNF
* (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces
* CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr()
* earlier we get DNF after exchanging ANDs and ORs again in the
@@ -1736,8 +1736,8 @@ check_targetlists_are_compatible(List *prev_target, List *current_target)
* union list is handed back but before that the remembered clauses
* (sortClause etc) are attached to the new top Node (Note that the
* new top Node can differ from the parsetree given as argument because of
- * the translation to DNF. That's why we have to remember the sortClause or
- * unique flag!) */
+ * the translation to DNF. That's why we have to remember the sortClause
+ * and so on!) */
static Query *
Except_Intersect_Rewrite(Query *parsetree)
{
@@ -1750,12 +1750,12 @@ Except_Intersect_Rewrite(Query *parsetree)
*intersect,
*intersectClause;
List *union_list = NIL,
- *sortClause;
+ *sortClause,
+ *distinctClause;
List *left_expr,
*right_expr,
*resnames = NIL;
char *op,
- *uniqueFlag,
*into;
bool isBinary,
isPortal,
@@ -1806,7 +1806,7 @@ Except_Intersect_Rewrite(Query *parsetree)
* node at the end of the function
*/
sortClause = parsetree->sortClause;
- uniqueFlag = parsetree->uniqueFlag;
+ distinctClause = parsetree->distinctClause;
into = parsetree->into;
isBinary = parsetree->isBinary;
isPortal = parsetree->isPortal;
@@ -2009,7 +2009,7 @@ Except_Intersect_Rewrite(Query *parsetree)
result->unionClause = lnext(union_list);
/* Attach all the items remembered in the beginning of the function */
result->sortClause = sortClause;
- result->uniqueFlag = uniqueFlag;
+ result->distinctClause = distinctClause;
result->into = into;
result->isPortal = isPortal;
result->isBinary = isBinary;
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 4d2960433e8..01f33ecf8f3 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -7,13 +7,14 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.43 2000/01/26 05:56:49 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.44 2000/01/27 18:11:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "optimizer/clauses.h"
+#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteManip.h"
@@ -286,22 +287,10 @@ AddGroupClause(Query *parsetree, List *group_by, List *tlist)
foreach(l, group_by)
{
GroupClause *groupclause = (GroupClause *) copyObject(lfirst(l));
- Index refnumber = groupclause->tleSortGroupRef;
- TargetEntry *tle = NULL;
- List *tl;
+ TargetEntry *tle = get_sortgroupclause_tle(groupclause, tlist);
- /* Find and copy the groupclause's TLE in the old tlist */
- foreach(tl, tlist)
- {
- if (((TargetEntry *) lfirst(tl))->resdom->ressortgroupref ==
- refnumber)
- {
- tle = (TargetEntry *) copyObject(lfirst(tl));
- break;
- }
- }
- if (tle == NULL)
- elog(ERROR, "AddGroupClause(): GROUP BY entry not found in rules targetlist");
+ /* copy the groupclause's TLE from the old tlist */
+ tle = (TargetEntry *) copyObject(tle);
/* The ressortgroupref number in the old tlist might be already
* taken in the new tlist, so force assignment of a new number.
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e8e2f971b01..d6d8ff6ffba 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: catversion.h,v 1.12 2000/01/26 05:57:56 momjian Exp $
+ * $Id: catversion.h,v 1.13 2000/01/27 18:11:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200001241
+#define CATALOG_VERSION_NO 200001271
#endif
diff --git a/src/include/executor/nodeGroup.h b/src/include/executor/nodeGroup.h
index afe510d5eaf..dd104b0af94 100644
--- a/src/include/executor/nodeGroup.h
+++ b/src/include/executor/nodeGroup.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: nodeGroup.h,v 1.14 2000/01/26 23:48:05 tgl Exp $
+ * $Id: nodeGroup.h,v 1.15 2000/01/27 18:11:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -22,4 +22,14 @@ extern int ExecCountSlotsGroup(Group *node);
extern void ExecEndGroup(Group *node);
extern void ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent);
+extern bool execTuplesMatch(HeapTuple tuple1,
+ HeapTuple tuple2,
+ TupleDesc tupdesc,
+ int numCols,
+ AttrNumber *matchColIdx,
+ FmgrInfo *eqfunctions);
+extern FmgrInfo *execTuplesMatchPrepare(TupleDesc tupdesc,
+ int numCols,
+ AttrNumber *matchColIdx);
+
#endif /* NODEGROUP_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a6a337087ae..827bb75a9c8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: execnodes.h,v 1.39 2000/01/26 05:58:15 momjian Exp $
+ * $Id: execnodes.h,v 1.40 2000/01/27 18:11:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -621,6 +621,7 @@ typedef struct AggState
typedef struct GroupState
{
CommonScanState csstate; /* its first field is NodeTag */
+ FmgrInfo *eqfunctions; /* per-field lookup data for equality fns */
bool grp_useFirstTuple; /* first tuple not processed yet */
bool grp_done;
HeapTuple grp_firstTuple;
@@ -663,9 +664,9 @@ typedef struct SortState
* Unique nodes are used "on top of" sort nodes to discard
* duplicate tuples returned from the sort phase. Basically
* all it does is compare the current tuple from the subplan
- * with the previously fetched tuple stored in OuterTuple and
- * if the two are identical, then we just fetch another tuple
- * from the sort and try again.
+ * with the previously fetched tuple stored in priorTuple.
+ * If the two are identical in all interesting fields, then
+ * we just fetch another tuple from the sort and try again.
*
* CommonState information
*
@@ -677,7 +678,12 @@ typedef struct SortState
* ScanAttributes attribute numbers of interest in this tuple
* ----------------
*/
-typedef CommonState UniqueState;
+typedef struct UniqueState
+{
+ CommonState cstate; /* its first field is NodeTag */
+ FmgrInfo *eqfunctions; /* per-field lookup data for equality fns */
+ HeapTuple priorTuple; /* most recently returned tuple, or NULL */
+} UniqueState;
/* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1346ac8a04e..288e7f96b8d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsenodes.h,v 1.96 2000/01/26 05:58:16 momjian Exp $
+ * $Id: parsenodes.h,v 1.97 2000/01/27 18:11:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -54,7 +54,8 @@ typedef struct Query
Node *qual; /* qualifications applied to tuples */
List *rowMark; /* list of RowMark entries */
- char *uniqueFlag; /* NULL, '*', or Unique attribute name */
+ List *distinctClause; /* a list of SortClause's */
+
List *sortClause; /* a list of SortClause's */
List *groupClause; /* a list of GroupClause's */
@@ -733,7 +734,8 @@ typedef struct InsertStmt
{
NodeTag type;
char *relname; /* relation to insert into */
- char *unique; /* NULL, '*', or unique attribute name */
+ List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
+ * lcons(NIL,NIL) for all (SELECT DISTINCT) */
List *cols; /* names of the columns */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the from clause */
@@ -777,7 +779,8 @@ typedef struct UpdateStmt
typedef struct SelectStmt
{
NodeTag type;
- char *unique; /* NULL, '*', or unique attribute name */
+ List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
+ * lcons(NIL,NIL) for all (SELECT DISTINCT) */
char *into; /* name of table (for select into table) */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the from clause */
@@ -1135,6 +1138,13 @@ typedef struct RangeTblEntry
* tleSortGroupRef must match ressortgroupref of exactly one Resdom of the
* associated targetlist; that is the expression to be sorted (or grouped) by.
* sortop is the OID of the ordering operator.
+ *
+ * SortClauses are also used to identify Resdoms that we will do a "Unique"
+ * filter step on (for SELECT DISTINCT and SELECT DISTINCT ON). The
+ * distinctClause list is simply a copy of the relevant members of the
+ * sortClause list. Note that distinctClause can be a subset of sortClause,
+ * but cannot have members not present in sortClause; and the members that
+ * do appear must be in the same order as in sortClause.
*/
typedef struct SortClause
{
@@ -1148,7 +1158,7 @@ typedef struct SortClause
* representation of GROUP BY clauses
*
* GroupClause is exactly like SortClause except for the nodetag value
- * (and it's probably not even really necessary to have two different
+ * (it's probably not even really necessary to have two different
* nodetags...). We have routines that operate interchangeably on both.
*/
typedef SortClause GroupClause;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 19216d36381..ff83431e580 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: plannodes.h,v 1.36 2000/01/26 05:58:16 momjian Exp $
+ * $Id: plannodes.h,v 1.37 2000/01/27 18:11:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -265,7 +265,7 @@ typedef struct Group
Plan plan;
bool tuplePerGroup; /* what tuples to return (see above) */
int numCols; /* number of group columns */
- AttrNumber *grpColIdx; /* index into the target list */
+ AttrNumber *grpColIdx; /* indexes into the target list */
GroupState *grpstate;
} Group;
@@ -314,10 +314,8 @@ typedef struct Unique
Plan plan; /* noname node flattened out */
Oid nonameid;
int keycount;
- char *uniqueAttr; /* NULL if all attrs, or unique attribute
- * name */
- AttrNumber uniqueAttrNum; /* attribute number of attribute to select
- * distinct on */
+ int numCols; /* number of columns to check for uniqueness */
+ AttrNumber *uniqColIdx; /* indexes into the target list */
UniqueState *uniquestate;
} Unique;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 826c5dbe3ce..340f54485cb 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: planmain.h,v 1.36 2000/01/26 05:58:20 momjian Exp $
+ * $Id: planmain.h,v 1.37 2000/01/27 18:11:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -33,7 +33,7 @@ extern Agg *make_agg(List *tlist, Plan *lefttree);
extern Group *make_group(List *tlist, bool tuplePerGroup, int ngrp,
AttrNumber *grpColIdx, Plan *lefttree);
extern Noname *make_noname(List *tlist, List *pathkeys, Plan *subplan);
-extern Unique *make_unique(List *tlist, Plan *lefttree, char *uniqueAttr);
+extern Unique *make_unique(List *tlist, Plan *lefttree, List *distinctList);
extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
/*
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 986c47ee796..dba93c0d173 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: tlist.h,v 1.23 2000/01/26 05:58:21 momjian Exp $
+ * $Id: tlist.h,v 1.24 2000/01/27 18:11:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,6 +28,9 @@ extern List *flatten_tlist(List *tlist);
extern List *add_to_flat_tlist(List *tlist, List *vars);
extern Var *get_expr(TargetEntry *tle);
+
+extern TargetEntry *get_sortgroupclause_tle(SortClause *sortClause,
+ List *targetList);
extern Node *get_sortgroupclause_expr(SortClause *sortClause,
List *targetList);
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 5d0b2badf16..235a02bc4c2 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parse_clause.h,v 1.14 2000/01/26 05:58:26 momjian Exp $
+ * $Id: parse_clause.h,v 1.15 2000/01/27 18:11:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -23,7 +23,9 @@ extern Node *transformWhereClause(ParseState *pstate, Node *where,
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
List *targetlist);
extern List *transformSortClause(ParseState *pstate, List *orderlist,
- List *targetlist, char *uniqueFlag);
+ List *targetlist);
+extern List *transformDistinctClause(ParseState *pstate, List *distinctlist,
+ List *targetlist, List **sortClause);
extern List *addAllTargetsToSortList(List *sortlist, List *targetlist);
extern Index assignSortGroupRef(TargetEntry *tle, List *tlist);
diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out
index 6a280f321a5..61f1dfd0129 100644
--- a/src/test/regress/expected/errors.out
+++ b/src/test/regress/expected/errors.out
@@ -29,12 +29,12 @@ ERROR: attribute 'nonesuch' not found
-- bad attribute name on rhs of operator
select * from pg_database where pg_database.datname = nonesuch;
ERROR: attribute 'nonesuch' not found
--- bad select distinct on syntax, distinct attribute missing
-select distinct on foobar from pg_database;
+-- bad select distinct on syntax, distinct attribute missing
+select distinct on (foobar) from pg_database;
ERROR: parser: parse error at or near "from"
-- bad select distinct on syntax, distinct attribute not in target list
-select distinct on foobar * from pg_database;
-ERROR: All fields in the UNIQUE ON clause must appear in the target list
+select distinct on (foobar) * from pg_database;
+ERROR: attribute 'foobar' not found
--
-- DELETE
diff --git a/src/test/regress/expected/select_distinct_on.out b/src/test/regress/expected/select_distinct_on.out
index 067f74db4f6..c4b03e95f76 100644
--- a/src/test/regress/expected/select_distinct_on.out
+++ b/src/test/regress/expected/select_distinct_on.out
@@ -1,18 +1,66 @@
--
-- SELECT_DISTINCT_ON
--
-SELECT DISTINCT ON string4 two, string4, ten
- FROM tmp
- ORDER BY two using <, string4 using <, ten using <;
- two | string4 | ten
------+---------+-----
- 0 | AAAAxx | 0
- 0 | HHHHxx | 0
- 0 | OOOOxx | 0
- 0 | VVVVxx | 0
- 1 | AAAAxx | 1
- 1 | HHHHxx | 1
- 1 | OOOOxx | 1
- 1 | VVVVxx | 1
-(8 rows)
+SELECT DISTINCT ON (string4) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using >, ten using <;
+ string4 | two | ten
+---------+-----+-----
+ AAAAxx | 1 | 1
+ HHHHxx | 1 | 1
+ OOOOxx | 1 | 1
+ VVVVxx | 1 | 1
+(4 rows)
+
+-- this will fail due to conflict of ordering requirements
+SELECT DISTINCT ON (string4, ten) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using <, ten using <;
+ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions
+SELECT DISTINCT ON (string4, ten) string4, ten, two
+ FROM tmp
+ ORDER BY string4 using <, ten using >, two using <;
+ string4 | ten | two
+---------+-----+-----
+ AAAAxx | 9 | 1
+ AAAAxx | 8 | 0
+ AAAAxx | 7 | 1
+ AAAAxx | 6 | 0
+ AAAAxx | 5 | 1
+ AAAAxx | 4 | 0
+ AAAAxx | 3 | 1
+ AAAAxx | 2 | 0
+ AAAAxx | 1 | 1
+ AAAAxx | 0 | 0
+ HHHHxx | 9 | 1
+ HHHHxx | 8 | 0
+ HHHHxx | 7 | 1
+ HHHHxx | 6 | 0
+ HHHHxx | 5 | 1
+ HHHHxx | 4 | 0
+ HHHHxx | 3 | 1
+ HHHHxx | 2 | 0
+ HHHHxx | 1 | 1
+ HHHHxx | 0 | 0
+ OOOOxx | 9 | 1
+ OOOOxx | 8 | 0
+ OOOOxx | 7 | 1
+ OOOOxx | 6 | 0
+ OOOOxx | 5 | 1
+ OOOOxx | 4 | 0
+ OOOOxx | 3 | 1
+ OOOOxx | 2 | 0
+ OOOOxx | 1 | 1
+ OOOOxx | 0 | 0
+ VVVVxx | 9 | 1
+ VVVVxx | 8 | 0
+ VVVVxx | 7 | 1
+ VVVVxx | 6 | 0
+ VVVVxx | 5 | 1
+ VVVVxx | 4 | 0
+ VVVVxx | 3 | 1
+ VVVVxx | 2 | 0
+ VVVVxx | 1 | 1
+ VVVVxx | 0 | 0
+(40 rows)
diff --git a/src/test/regress/sql/errors.sql b/src/test/regress/sql/errors.sql
index cd43cb909b0..ffcb5fcdb74 100644
--- a/src/test/regress/sql/errors.sql
+++ b/src/test/regress/sql/errors.sql
@@ -34,12 +34,12 @@ select * from pg_database where nonesuch = pg_database.datname;
select * from pg_database where pg_database.datname = nonesuch;
--- bad select distinct on syntax, distinct attribute missing
-select distinct on foobar from pg_database;
+-- bad select distinct on syntax, distinct attribute missing
+select distinct on (foobar) from pg_database;
-- bad select distinct on syntax, distinct attribute not in target list
-select distinct on foobar * from pg_database;
+select distinct on (foobar) * from pg_database;
--
diff --git a/src/test/regress/sql/select_distinct_on.sql b/src/test/regress/sql/select_distinct_on.sql
index 87001a0c088..54d98ca6979 100644
--- a/src/test/regress/sql/select_distinct_on.sql
+++ b/src/test/regress/sql/select_distinct_on.sql
@@ -2,7 +2,15 @@
-- SELECT_DISTINCT_ON
--
-SELECT DISTINCT ON string4 two, string4, ten
- FROM tmp
- ORDER BY two using <, string4 using <, ten using <;
+SELECT DISTINCT ON (string4) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using >, ten using <;
+-- this will fail due to conflict of ordering requirements
+SELECT DISTINCT ON (string4, ten) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using <, ten using <;
+
+SELECT DISTINCT ON (string4, ten) string4, ten, two
+ FROM tmp
+ ORDER BY string4 using <, ten using >, two using <;