OSDN Git Service

aggregate(DISTINCT ...) works, per SQL spec.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 13 Dec 1999 01:27:21 +0000 (01:27 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 13 Dec 1999 01:27:21 +0000 (01:27 +0000)
Note this forces initdb because of change of Aggref node in stored rules.

16 files changed:
src/backend/executor/nodeAgg.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/util/clauses.c
src/backend/parser/parse_agg.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/sort/tuplesort.c
src/include/catalog/catversion.h
src/include/nodes/primnodes.h
src/include/optimizer/clauses.h
src/include/utils/tuplesort.h
src/test/regress/expected/aggregates.out
src/test/regress/expected/rules.out
src/test/regress/sql/aggregates.sql

index 0956af4..0a95c92 100644 (file)
@@ -3,15 +3,35 @@
  * nodeAgg.c
  *       Routines to handle aggregate nodes.
  *
- * Copyright (c) 1994, Regents of the University of California
+ *       ExecAgg evaluates each aggregate in the following steps: (initcond1,
+ *       initcond2 are the initial values and sfunc1, sfunc2, and finalfunc are
+ *       the transition functions.)
+ *
+ *              value1 = initcond1
+ *              value2 = initcond2
+ *              foreach input_value do
+ *                     value1 = sfunc1(value1, input_value)
+ *                     value2 = sfunc2(value2)
+ *              value1 = finalfunc(value1, value2)
+ *
+ *       If initcond1 is NULL then the first non-NULL input_value is
+ *       assigned directly to value1.  sfunc1 isn't applied until value1
+ *       is non-NULL.
+ *
+ *       sfunc1 is never applied when the current tuple's input_value is NULL.
+ *       sfunc2 is applied for each tuple if the aggref is marked 'usenulls',
+ *       otherwise it is only applied when input_value is not NULL.
+ *       (usenulls was formerly used for COUNT(*), but is no longer needed for
+ *       that purpose; as of 10/1999 the support for usenulls is dead code.
+ *       I have not removed it because it seems like a potentially useful
+ *       feature for user-defined aggregates.  We'd just need to add a
+ *       flag column to pg_aggregate and a parameter to CREATE AGGREGATE...)
  *
  *
- * NOTE
- *       The implementation of Agg node has been reworked to handle legal
- *       SQL aggregates. (Do not expect POSTQUEL semantics.)    -- ay 2/95
+ * Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.59 1999/10/30 02:35:14 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.60 1999/12/13 01:26:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "access/heapam.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "executor/nodeAgg.h"
 #include "optimizer/clauses.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_type.h"
 #include "utils/syscache.h"
+#include "utils/tuplesort.h"
 
 /*
  * AggStatePerAggData - per-aggregate working state for the Agg scan
@@ -36,6 +60,9 @@ typedef struct AggStatePerAggData
         * thereafter:
         */
 
+       /* Link to Aggref node this working state is for */
+       Aggref     *aggref;
+
        /* Oids of transfer functions */
        Oid                     xfn1_oid;
        Oid                     xfn2_oid;
@@ -48,6 +75,18 @@ typedef struct AggStatePerAggData
        FmgrInfo        xfn2;
        FmgrInfo        finalfn;
        /*
+        * Type of input data and Oid of sort operator to use for it;
+        * only set/used when aggregate has DISTINCT flag.  (These are not
+        * used directly by nodeAgg, but must be passed to the Tuplesort object.)
+        */
+       Oid                     inputType;
+       Oid                     sortOperator;
+       /*
+        * fmgr lookup data for input type's equality operator --- only set/used
+        * when aggregate has DISTINCT flag.
+        */
+       FmgrInfo        equalfn;
+       /*
         * initial values from pg_aggregate entry
         */
        Datum           initValue1;             /* for transtype1 */
@@ -55,19 +94,29 @@ typedef struct AggStatePerAggData
        bool            initValue1IsNull,
                                initValue2IsNull;
        /*
-        * We need the len and byval info for the agg's transition status types
-        * in order to know how to copy/delete values.
+        * We need the len and byval info for the agg's input and transition
+        * data types in order to know how to copy/delete values.
         */
-       int                     transtype1Len,
+       int                     inputtypeLen,
+                               transtype1Len,
                                transtype2Len;
-       bool            transtype1ByVal,
+       bool            inputtypeByVal,
+                               transtype1ByVal,
                                transtype2ByVal;
 
        /*
         * These values are working state that is initialized at the start
-        * of an input tuple group and updated for each input tuple:
+        * of an input tuple group and updated for each input tuple.
+        *
+        * For a simple (non DISTINCT) aggregate, we just feed the input values
+        * straight to the transition functions.  If it's DISTINCT, we pass the
+        * input values into a Tuplesort object; then at completion of the input
+        * tuple group, we scan the sorted values, eliminate duplicates, and run
+        * the transition functions on the rest.
         */
 
+       Tuplesortstate *sortstate;      /* sort object, if a DISTINCT agg */
+
        Datum           value1,                 /* current transfer values 1 and 2 */
                                value2;
        bool            value1IsNull,
@@ -82,28 +131,248 @@ typedef struct AggStatePerAggData
 } AggStatePerAggData;
 
 
+static void initialize_aggregate (AggStatePerAgg peraggstate);
+static void advance_transition_functions (AggStatePerAgg peraggstate,
+                                                                                 Datum newVal, bool isNull);
+static void finalize_aggregate (AggStatePerAgg peraggstate,
+                                                               Datum *resultVal, bool *resultIsNull);
+static Datum copyDatum(Datum val, int typLen, bool typByVal);
+
+
 /*
- * Helper routine to make a copy of a Datum.
- *
- * NB: input had better not be a NULL; might cause null-pointer dereference.
+ * Initialize one aggregate for a new set of input values.
  */
-static Datum
-copyDatum(Datum val, int typLen, bool typByVal)
+static void
+initialize_aggregate (AggStatePerAgg peraggstate)
 {
-       if (typByVal)
-               return val;
+       Aggref             *aggref = peraggstate->aggref;
+
+       /*
+        * Start a fresh sort operation for each DISTINCT aggregate.
+        */
+       if (aggref->aggdistinct)
+       {
+               /* In case of rescan, maybe there could be an uncompleted
+                * sort operation?  Clean it up if so.
+                */
+               if (peraggstate->sortstate)
+                       tuplesort_end(peraggstate->sortstate);
+
+               peraggstate->sortstate =
+                       tuplesort_begin_datum(peraggstate->inputType,
+                                                                 peraggstate->sortOperator,
+                                                                 false);
+       }
+
+       /*
+        * (Re)set value1 and value2 to their initial values.
+        */
+       if (OidIsValid(peraggstate->xfn1_oid) &&
+               ! peraggstate->initValue1IsNull)
+               peraggstate->value1 = copyDatum(peraggstate->initValue1, 
+                                                                               peraggstate->transtype1Len,
+                                                                               peraggstate->transtype1ByVal);
+       else
+               peraggstate->value1 = (Datum) NULL;
+       peraggstate->value1IsNull = peraggstate->initValue1IsNull;
+
+       if (OidIsValid(peraggstate->xfn2_oid) &&
+               ! peraggstate->initValue2IsNull)
+               peraggstate->value2 = copyDatum(peraggstate->initValue2, 
+                                                                               peraggstate->transtype2Len,
+                                                                               peraggstate->transtype2ByVal);
        else
+               peraggstate->value2 = (Datum) NULL;
+       peraggstate->value2IsNull = peraggstate->initValue2IsNull;
+
+       /* ------------------------------------------
+        * If the initial value for the first transition function
+        * doesn't exist in the pg_aggregate table then we will let
+        * the first value returned from the outer procNode become
+        * the initial value. (This is useful for aggregates like
+        * max{} and min{}.)  The noInitValue flag signals that we
+        * still need to do this.
+        * ------------------------------------------
+        */
+       peraggstate->noInitValue = peraggstate->initValue1IsNull;
+}
+
+/*
+ * Given a new input value, advance the transition functions of an aggregate.
+ *
+ * Note: if the agg does not have usenulls set, null inputs will be filtered
+ * out before reaching here.
+ */
+static void
+advance_transition_functions (AggStatePerAgg peraggstate,
+                                                         Datum newVal, bool isNull)
+{
+       Datum           args[2];
+
+       if (OidIsValid(peraggstate->xfn1_oid) && !isNull)
        {
-               char   *newVal;
+               if (peraggstate->noInitValue)
+               {
+                       /*
+                        * value1 has not been initialized. This is the first non-NULL
+                        * input value. We use it as the initial value for value1.
+                        *
+                        * XXX We assume, without having checked, that the agg's input
+                        * type is binary-compatible with its transtype1!
+                        *
+                        * We have to copy the datum since the tuple from which it came
+                        * will be freed on the next iteration of the scan.
+                        */
+                       peraggstate->value1 = copyDatum(newVal,
+                                                                                       peraggstate->transtype1Len,
+                                                                                       peraggstate->transtype1ByVal);
+                       peraggstate->value1IsNull = false;
+                       peraggstate->noInitValue = false;
+               }
+               else
+               {
+                       /* apply transition function 1 */
+                       args[0] = peraggstate->value1;
+                       args[1] = newVal;
+                       newVal = (Datum) fmgr_c(&peraggstate->xfn1,
+                                                                       (FmgrValues *) args,
+                                                                       &isNull);
+                       if (! peraggstate->transtype1ByVal)
+                               pfree(peraggstate->value1);
+                       peraggstate->value1 = newVal;
+               }
+       }
 
-               if (typLen == -1)               /* variable length type? */
-                       typLen = VARSIZE((struct varlena *) DatumGetPointer(val));
-               newVal = (char *) palloc(typLen);
-               memcpy(newVal, DatumGetPointer(val), typLen);
-               return PointerGetDatum(newVal);
+       if (OidIsValid(peraggstate->xfn2_oid))
+       {
+               /* apply transition function 2 */
+               args[0] = peraggstate->value2;
+               isNull = false;                 /* value2 cannot be null, currently */
+               newVal = (Datum) fmgr_c(&peraggstate->xfn2,
+                                                               (FmgrValues *) args,
+                                                               &isNull);
+               if (! peraggstate->transtype2ByVal)
+                       pfree(peraggstate->value2);
+               peraggstate->value2 = newVal;
        }
 }
 
+/*
+ * Compute the final value of one aggregate.
+ */
+static void
+finalize_aggregate (AggStatePerAgg peraggstate,
+                                       Datum *resultVal, bool *resultIsNull)
+{
+       Aggref     *aggref = peraggstate->aggref;
+       char       *args[2];
+
+       /*
+        * If it's a DISTINCT aggregate, all we've done so far is to stuff the
+        * input values into the sort object.  Complete the sort, then run
+        * the transition functions on the non-duplicate values.  Note that
+        * DISTINCT always suppresses nulls, per SQL spec, regardless of usenulls.
+        */
+       if (aggref->aggdistinct)
+       {
+               Datum           oldVal = (Datum) 0;
+               bool            haveOldVal = false;
+               Datum           newVal;
+               bool            isNull;
+
+               tuplesort_performsort(peraggstate->sortstate);
+               while (tuplesort_getdatum(peraggstate->sortstate, true,
+                                                                 &newVal, &isNull))
+               {
+                       if (isNull)
+                               continue;
+                       if (haveOldVal)
+                       {
+                               Datum   equal;
+
+                               equal = (Datum) (*fmgr_faddr(&peraggstate->equalfn)) (oldVal,
+                                                                                                                                         newVal);
+                               if (DatumGetInt32(equal) != 0)
+                               {
+                                       if (! peraggstate->inputtypeByVal)
+                                               pfree(DatumGetPointer(newVal));
+                                       continue;
+                               }
+                       }
+                       advance_transition_functions(peraggstate, newVal, false);
+                       if (haveOldVal && ! peraggstate->inputtypeByVal)
+                               pfree(DatumGetPointer(oldVal));
+                       oldVal = newVal;
+                       haveOldVal = true;
+               }
+               if (haveOldVal && ! peraggstate->inputtypeByVal)
+                       pfree(DatumGetPointer(oldVal));
+               tuplesort_end(peraggstate->sortstate);
+               peraggstate->sortstate = NULL;
+       }
+
+       /*
+        * Now apply the agg's finalfn, or substitute the appropriate transition
+        * value if there is no finalfn.
+        *
+        * XXX For now, only apply finalfn if we got at least one
+        * non-null input value.  This prevents zero divide in AVG().
+        * If we had cleaner handling of null inputs/results in functions,
+        * we could probably take out this hack and define the result
+        * for no inputs as whatever finalfn returns for null input.
+        */
+       if (OidIsValid(peraggstate->finalfn_oid) &&
+               ! peraggstate->noInitValue)
+       {
+               if (peraggstate->finalfn.fn_nargs > 1)
+               {
+                       args[0] = (char *) peraggstate->value1;
+                       args[1] = (char *) peraggstate->value2;
+               }
+               else if (OidIsValid(peraggstate->xfn1_oid))
+                       args[0] = (char *) peraggstate->value1;
+               else if (OidIsValid(peraggstate->xfn2_oid))
+                       args[0] = (char *) peraggstate->value2;
+               else
+                       elog(ERROR, "ExecAgg: no valid transition functions??");
+               *resultIsNull = false;
+               *resultVal = (Datum) fmgr_c(&peraggstate->finalfn,
+                                                                       (FmgrValues *) args,
+                                                                       resultIsNull);
+       }
+       else if (OidIsValid(peraggstate->xfn1_oid))
+       {
+               /* Return value1 */
+               *resultVal = peraggstate->value1;
+               *resultIsNull = peraggstate->value1IsNull;
+               /* prevent pfree below */
+               peraggstate->value1IsNull = true;
+       }
+       else if (OidIsValid(peraggstate->xfn2_oid))
+       {
+               /* Return value2 */
+               *resultVal = peraggstate->value2;
+               *resultIsNull = peraggstate->value2IsNull;
+               /* prevent pfree below */
+               peraggstate->value2IsNull = true;
+       }
+       else
+               elog(ERROR, "ExecAgg: no valid transition functions??");
+
+       /*
+        * Release any per-group working storage, unless we're passing
+        * it back as the result of the aggregate.
+        */
+       if (OidIsValid(peraggstate->xfn1_oid) &&
+               ! peraggstate->value1IsNull &&
+               ! peraggstate->transtype1ByVal)
+               pfree(peraggstate->value1);
+       
+       if (OidIsValid(peraggstate->xfn2_oid) &&
+               ! peraggstate->value2IsNull &&
+               ! peraggstate->transtype2ByVal)
+               pfree(peraggstate->value2);
+}
 
 /* ---------------------------------------
  *
@@ -118,30 +387,6 @@ copyDatum(Datum val, int typLen, bool typByVal)
  *       the expression context to be used when ExecProject evaluates the
  *       result tuple.
  *
- *       ExecAgg evaluates each aggregate in the following steps: (initcond1,
- *       initcond2 are the initial values and sfunc1, sfunc2, and finalfunc are
- *       the transition functions.)
- *
- *              value1 = initcond1
- *              value2 = initcond2
- *              foreach tuple do
- *                     value1 = sfunc1(value1, aggregated_value)
- *                     value2 = sfunc2(value2)
- *              value1 = finalfunc(value1, value2)
- *
- *       If initcond1 is NULL then the first non-NULL aggregated_value is
- *       assigned directly to value1.  sfunc1 isn't applied until value1
- *       is non-NULL.
- *
- *       sfunc1 is never applied when the current tuple's aggregated_value
- *       is NULL.  sfunc2 is applied for each tuple if the aggref is marked
- *       'usenulls', otherwise it is only applied when aggregated_value is
- *       not NULL.  (usenulls was formerly used for COUNT(*), but is no longer
- *       needed for that purpose; as of 10/1999 the support for usenulls is
- *       dead code.  I have not removed it because it seems like a potentially
- *       useful feature for user-defined aggregates.  We'd just need to add a
- *       flag column to pg_aggregate and a parameter to CREATE AGGREGATE...)
- *
  *       If the outer subplan is a Group node, ExecAgg returns as many tuples
  *       as there are groups.
  *
@@ -161,7 +406,6 @@ ExecAgg(Agg *node)
        TupleTableSlot *resultSlot;
        HeapTuple       inputTuple;
        int                     aggno;
-       List       *alist;
        bool            isDone;
        bool            isNull;
 
@@ -190,42 +434,11 @@ ExecAgg(Agg *node)
                /*
                 * Initialize working state for a new input tuple group
                 */
-               aggno = -1;
-               foreach(alist, aggstate->aggs)
+               for (aggno = 0; aggno < aggstate->numaggs; aggno++)
                {
-                       AggStatePerAgg  peraggstate = &peragg[++aggno];
+                       AggStatePerAgg  peraggstate = &peragg[aggno];
 
-                       /*
-                        * (Re)set value1 and value2 to their initial values.
-                        */
-                       if (OidIsValid(peraggstate->xfn1_oid) &&
-                               ! peraggstate->initValue1IsNull)
-                               peraggstate->value1 = copyDatum(peraggstate->initValue1, 
-                                                                                               peraggstate->transtype1Len,
-                                                                                               peraggstate->transtype1ByVal);
-                       else
-                               peraggstate->value1 = (Datum) NULL;
-                       peraggstate->value1IsNull = peraggstate->initValue1IsNull;
-
-                       if (OidIsValid(peraggstate->xfn2_oid) &&
-                               ! peraggstate->initValue2IsNull)
-                               peraggstate->value2 = copyDatum(peraggstate->initValue2, 
-                                                                                               peraggstate->transtype2Len,
-                                                                                               peraggstate->transtype2ByVal);
-                       else
-                               peraggstate->value2 = (Datum) NULL;
-                       peraggstate->value2IsNull = peraggstate->initValue2IsNull;
-
-                       /* ------------------------------------------
-                        * If the initial value for the first transition function
-                        * doesn't exist in the pg_aggregate table then we will let
-                        * the first value returned from the outer procNode become
-                        * the initial value. (This is useful for aggregates like
-                        * max{} and min{}.)  The noInitValue flag signals that we
-                        * still need to do this.
-                        * ------------------------------------------
-                        */
-                       peraggstate->noInitValue = peraggstate->initValue1IsNull;
+                       initialize_aggregate(peraggstate);
                }
 
                inputTuple = NULL;              /* no saved input tuple yet */
@@ -243,13 +456,11 @@ ExecAgg(Agg *node)
                                break;
                        econtext->ecxt_scantuple = outerslot;
 
-                       aggno = -1;
-                       foreach(alist, aggstate->aggs)
+                       for (aggno = 0; aggno < aggstate->numaggs; aggno++)
                        {
-                               Aggref             *aggref = (Aggref *) lfirst(alist);
-                               AggStatePerAgg  peraggstate = &peragg[++aggno];
+                               AggStatePerAgg  peraggstate = &peragg[aggno];
+                               Aggref             *aggref = peraggstate->aggref;
                                Datum                   newVal;
-                               Datum                   args[2];
 
                                newVal = ExecEvalExpr(aggref->target, econtext,
                                                                          &isNull, &isDone);
@@ -257,53 +468,12 @@ ExecAgg(Agg *node)
                                if (isNull && !aggref->usenulls)
                                        continue;       /* ignore this tuple for this agg */
 
-                               if (OidIsValid(peraggstate->xfn1_oid) && !isNull)
-                               {
-                                       if (peraggstate->noInitValue)
-                                       {
-                                               /*
-                                                * value1 has not been initialized. This is the
-                                                * first non-NULL input value. We use it as the
-                                                * initial value for value1.  XXX We assume,
-                                                * without having checked, that the agg's input type
-                                                * is binary-compatible with its transtype1!
-                                                *
-                                                * We have to copy the datum since the tuple from
-                                                * which it came will be freed on the next iteration
-                                                * of the scan.  
-                                                */
-                                               peraggstate->value1 = copyDatum(newVal,
-                                                                                               peraggstate->transtype1Len,
-                                                                                               peraggstate->transtype1ByVal);
-                                               peraggstate->value1IsNull = false;
-                                               peraggstate->noInitValue = false;
-                                       }
-                                       else
-                                       {
-                                               /* apply transition function 1 */
-                                               args[0] = peraggstate->value1;
-                                               args[1] = newVal;
-                                               newVal = (Datum) fmgr_c(&peraggstate->xfn1,
-                                                                                               (FmgrValues *) args,
-                                                                                               &isNull);
-                                               if (! peraggstate->transtype1ByVal)
-                                                       pfree(peraggstate->value1);
-                                               peraggstate->value1 = newVal;
-                                       }
-                               }
-
-                               if (OidIsValid(peraggstate->xfn2_oid))
-                               {
-                                       /* apply transition function 2 */
-                                       args[0] = peraggstate->value2;
-                                       isNull = false; /* value2 cannot be null, currently */
-                                       newVal = (Datum) fmgr_c(&peraggstate->xfn2,
-                                                                                       (FmgrValues *) args,
-                                                                                       &isNull);
-                                       if (! peraggstate->transtype2ByVal)
-                                               pfree(peraggstate->value2);
-                                       peraggstate->value2 = newVal;
-                               }
+                               if (aggref->aggdistinct)
+                                       tuplesort_putdatum(peraggstate->sortstate,
+                                                                          newVal, isNull);
+                               else
+                                       advance_transition_functions(peraggstate,
+                                                                                                newVal, isNull);
                        }
 
                        /*
@@ -320,70 +490,12 @@ ExecAgg(Agg *node)
                 * Done scanning input tuple group.
                 * Finalize each aggregate calculation.
                 */
-               aggno = -1;
-               foreach(alist, aggstate->aggs)
+               for (aggno = 0; aggno < aggstate->numaggs; aggno++)
                {
-                       AggStatePerAgg  peraggstate = &peragg[++aggno];
-                       char               *args[2];
-
-                       /*
-                        * XXX For now, only apply finalfn if we got at least one
-                        * non-null input value.  This prevents zero divide in AVG().
-                        * If we had cleaner handling of null inputs/results in functions,
-                        * we could probably take out this hack and define the result
-                        * for no inputs as whatever finalfn returns for null input.
-                        */
-                       if (OidIsValid(peraggstate->finalfn_oid) &&
-                               ! peraggstate->noInitValue)
-                       {
-                               if (peraggstate->finalfn.fn_nargs > 1)
-                               {
-                                       args[0] = (char *) peraggstate->value1;
-                                       args[1] = (char *) peraggstate->value2;
-                               }
-                               else if (OidIsValid(peraggstate->xfn1_oid))
-                                       args[0] = (char *) peraggstate->value1;
-                               else if (OidIsValid(peraggstate->xfn2_oid))
-                                       args[0] = (char *) peraggstate->value2;
-                               else
-                                       elog(ERROR, "ExecAgg: no valid transition functions??");
-                               aggnulls[aggno] = false;
-                               aggvalues[aggno] = (Datum) fmgr_c(&peraggstate->finalfn,
-                                                                                                 (FmgrValues *) args,
-                                                                                                 &(aggnulls[aggno]));
-                       }
-                       else if (OidIsValid(peraggstate->xfn1_oid))
-                       {
-                               /* Return value1 */
-                               aggvalues[aggno] = peraggstate->value1;
-                               aggnulls[aggno] = peraggstate->value1IsNull;
-                               /* prevent pfree below */
-                               peraggstate->value1IsNull = true;
-                       }
-                       else if (OidIsValid(peraggstate->xfn2_oid))
-                       {
-                               /* Return value2 */
-                               aggvalues[aggno] = peraggstate->value2;
-                               aggnulls[aggno] = peraggstate->value2IsNull;
-                               /* prevent pfree below */
-                               peraggstate->value2IsNull = true;
-                       }
-                       else
-                               elog(ERROR, "ExecAgg: no valid transition functions??");
-
-                       /*
-                        * Release any per-group working storage, unless we're passing
-                        * it back as the result of the aggregate.
-                        */
-                       if (OidIsValid(peraggstate->xfn1_oid) &&
-                               ! peraggstate->value1IsNull &&
-                               ! peraggstate->transtype1ByVal)
-                               pfree(peraggstate->value1);
+                       AggStatePerAgg  peraggstate = &peragg[aggno];
 
-                       if (OidIsValid(peraggstate->xfn2_oid) &&
-                               ! peraggstate->value2IsNull &&
-                               ! peraggstate->transtype2ByVal)
-                               pfree(peraggstate->value2);
+                       finalize_aggregate(peraggstate,
+                                                          & aggvalues[aggno], & aggnulls[aggno]);
                }
 
                /*
@@ -458,14 +570,14 @@ ExecAgg(Agg *node)
 
                /*
                 * Form a projection tuple using the aggregate results and the
-                * representative input tuple.  Store it in the result tuple slot,
-                * and return it if it meets my qual condition.
+                * representative input tuple.  Store it in the result tuple slot.
                 */
                resultSlot = ExecProject(projInfo, &isDone);
 
                /*
                 * If the completed tuple does not match the qualifications,
                 * it is ignored and we loop back to try to process another group.
+                * Otherwise, return the tuple.
                 */
        }
        while (! ExecQual(node->plan.qual, econtext));
@@ -505,6 +617,11 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent)
 
        /*
         * find aggregates in targetlist and quals
+        *
+        * Note: pull_agg_clauses also checks that no aggs contain other agg
+        * calls in their arguments.  This would make no sense under SQL semantics
+        * anyway (and it's forbidden by the spec).  Because that is true, we
+        * don't need to worry about evaluating the aggs in any particular order.
         */
        aggstate->aggs = nconc(pull_agg_clause((Node *) node->plan.targetlist),
                                                   pull_agg_clause((Node *) node->plan.qual));
@@ -588,6 +705,9 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent)
                /* Mark Aggref node with its associated index in the result array */
                aggref->aggno = aggno;
 
+               /* Fill in the peraggstate data */
+               peraggstate->aggref = aggref;
+
                aggTuple = SearchSysCacheTuple(AGGNAME,
                                                                           PointerGetDatum(aggname),
                                                                           ObjectIdGetDatum(aggref->basetype),
@@ -644,6 +764,29 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent)
                {
                        fmgr_info(finalfn_oid, &peraggstate->finalfn);
                }
+
+               if (aggref->aggdistinct)
+               {
+                       Oid                     inputType = exprType(aggref->target);
+                       Operator        eq_operator;
+                       Form_pg_operator pgopform;
+
+                       peraggstate->inputType = inputType;
+                       typeInfo = typeidType(inputType);
+                       peraggstate->inputtypeLen = typeLen(typeInfo);
+                       peraggstate->inputtypeByVal = typeByVal(typeInfo);
+
+                       eq_operator = oper("=", inputType, inputType, true);
+                       if (!HeapTupleIsValid(eq_operator))
+                       {
+                               elog(ERROR, "Unable to identify an equality operator for type '%s'",
+                                        typeidTypeName(inputType));
+                       }
+                       pgopform = (Form_pg_operator) GETSTRUCT(eq_operator);
+                       fmgr_info(pgopform->oprcode, &(peraggstate->equalfn));
+                       peraggstate->sortOperator = any_ordering_op(inputType);
+                       peraggstate->sortstate = NULL;
+               }
        }
 
        return TRUE;
@@ -690,3 +833,26 @@ ExecReScanAgg(Agg *node, ExprContext *exprCtxt, Plan *parent)
                ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
 
 }
+
+
+/*
+ * Helper routine to make a copy of a Datum.
+ *
+ * NB: input had better not be a NULL; might cause null-pointer dereference.
+ */
+static Datum
+copyDatum(Datum val, int typLen, bool typByVal)
+{
+       if (typByVal)
+               return val;
+       else
+       {
+               char   *newVal;
+
+               if (typLen == -1)               /* variable length type? */
+                       typLen = VARSIZE((struct varlena *) DatumGetPointer(val));
+               newVal = (char *) palloc(typLen);
+               memcpy(newVal, DatumGetPointer(val), typLen);
+               return PointerGetDatum(newVal);
+       }
+}
index 1b2726f..884926b 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.97 1999/11/23 20:06:52 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.98 1999/12/13 01:26:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -832,6 +832,8 @@ _copyAggref(Aggref *from)
        newnode->aggtype = from->aggtype;
        Node_Copy(from, newnode, target);
        newnode->usenulls = from->usenulls;
+       newnode->aggstar = from->aggstar;
+       newnode->aggdistinct = from->aggdistinct;
        newnode->aggno = from->aggno; /* probably not needed */
 
        return newnode;
index b35b271..f70fe50 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.52 1999/11/23 20:06:52 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.53 1999/12/13 01:26:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -219,6 +219,10 @@ _equalAggref(Aggref *a, Aggref *b)
                return false;
        if (a->usenulls != b->usenulls)
                return false;
+       if (a->aggstar != b->aggstar)
+               return false;
+       if (a->aggdistinct != b->aggdistinct)
+               return false;
        /* ignore aggno, which is only a private field for the executor */
        return true;
 }
index 78bda61..7907f1b 100644 (file)
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *     $Id: outfuncs.c,v 1.99 1999/12/10 07:37:31 tgl Exp $
+ *     $Id: outfuncs.c,v 1.100 1999/12/13 01:26:53 tgl Exp $
  *
  * NOTES
  *       Every (plan) node in POSTGRES has an associated "out" routine which
@@ -680,14 +680,17 @@ static void
 _outAggref(StringInfo str, Aggref *node)
 {
        appendStringInfo(str,
-                                " AGGREG :aggname %s :basetype %u :aggtype %u :target ",
+                                        " AGGREG :aggname %s :basetype %u :aggtype %u :target ",
                                         stringStringInfo(node->aggname),
                                         node->basetype,
                                         node->aggtype);
        _outNode(str, node->target);
 
-       appendStringInfo(str, " :usenulls %s ",
-                                        node->usenulls ? "true" : "false");
+       appendStringInfo(str, " :usenulls %s :aggstar %s :aggdistinct %s ",
+                                        node->usenulls ? "true" : "false",
+                                        node->aggstar ? "true" : "false",
+                                        node->aggdistinct ? "true" : "false");
+       /* aggno is not dumped */
 }
 
 /*
index 99be519..83683ff 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.75 1999/11/23 20:06:53 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.76 1999/12/13 01:26:54 tgl Exp $
  *
  * NOTES
  *       Most of the read functions for plan nodes are tested. (In fact, they
@@ -1190,6 +1190,14 @@ _readAggref()
        token = lsptok(NULL, &length);          /* get usenulls */
        local_node->usenulls = (token[0] == 't') ? true : false;
 
+       token = lsptok(NULL, &length);          /* eat :aggstar */
+       token = lsptok(NULL, &length);          /* get aggstar */
+       local_node->aggstar = (token[0] == 't') ? true : false;
+
+       token = lsptok(NULL, &length);          /* eat :aggdistinct */
+       token = lsptok(NULL, &length);          /* get aggdistinct */
+       local_node->aggdistinct = (token[0] == 't') ? true : false;
+
        return local_node;
 }
 
index 63b3ff8..63eebae 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.56 1999/12/09 05:58:53 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.57 1999/12/13 01:26:55 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -45,6 +45,7 @@ typedef struct {
        List       *targetList;
 } check_subplans_for_ungrouped_vars_context;
 
+static bool contain_agg_clause_walker(Node *node, void *context);
 static bool pull_agg_clause_walker(Node *node, List **listptr);
 static bool check_subplans_for_ungrouped_vars_walker(Node *node,
                                        check_subplans_for_ungrouped_vars_context *context);
@@ -394,11 +395,35 @@ pull_constant_clauses(List *quals, List **constantQual)
 }
 
 /*
+ * contain_agg_clause
+ *       Recursively search for Aggref nodes within a clause.
+ *
+ *       Returns true if any aggregate found.
+ */
+bool
+contain_agg_clause(Node *clause)
+{
+       return contain_agg_clause_walker(clause, NULL);
+}
+
+static bool
+contain_agg_clause_walker(Node *node, void *context)
+{
+       if (node == NULL)
+               return false;
+       if (IsA(node, Aggref))
+               return true;                    /* abort the tree traversal and return true */
+       return expression_tree_walker(node, contain_agg_clause_walker, context);
+}
+
+/*
  * pull_agg_clause
  *       Recursively pulls all Aggref nodes from an expression tree.
  *
  *       Returns list of Aggref nodes found.  Note the nodes themselves are not
  *       copied, only referenced.
+ *
+ *       Note: this also checks for nested aggregates, which are an error.
  */
 List *
 pull_agg_clause(Node *clause)
@@ -417,9 +442,16 @@ pull_agg_clause_walker(Node *node, List **listptr)
        if (IsA(node, Aggref))
        {
                *listptr = lappend(*listptr, node);
-               /* continue, to iterate over agg's arg as well (do nested aggregates
-                * actually work?)
+               /*
+                * Complain if the aggregate's argument contains any aggregates;
+                * nested agg functions are semantically nonsensical.
+                */
+               if (contain_agg_clause(((Aggref *) node)->target))
+                       elog(ERROR, "Aggregate function calls may not be nested");
+               /*
+                * Having checked that, we need not recurse into the argument.
                 */
+               return false;
        }
        return expression_tree_walker(node, pull_agg_clause_walker,
                                                                  (void *) listptr);
index 68280f7..21f8efe 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.31 1999/12/10 07:37:35 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.32 1999/12/13 01:26:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,39 +28,12 @@ typedef struct {
        List       *groupClauses;
 } check_ungrouped_columns_context;
 
-static bool contain_agg_clause(Node *clause);
-static bool contain_agg_clause_walker(Node *node, void *context);
 static void check_ungrouped_columns(Node *node, ParseState *pstate,
                                                                        List *groupClauses);
 static bool check_ungrouped_columns_walker(Node *node,
                                                                                   check_ungrouped_columns_context *context);
 
 /*
- * contain_agg_clause
- *       Recursively find aggref nodes within a clause.
- *
- *       Returns true if any aggregate found.
- *
- * NOTE: we assume that the given clause has been transformed suitably for
- * parser output.  This means we can use the planner's expression_tree_walker.
- */
-static bool
-contain_agg_clause(Node *clause)
-{
-       return contain_agg_clause_walker(clause, NULL);
-}
-
-static bool
-contain_agg_clause_walker(Node *node, void *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, Aggref))
-               return true;                    /* abort the tree traversal and return true */
-       return expression_tree_walker(node, contain_agg_clause_walker, context);
-}
-
-/*
  * check_ungrouped_columns -
  *       Scan the given expression tree for ungrouped variables (variables
  *       that are not listed in the groupClauses list and are not within
@@ -232,7 +205,8 @@ ParseAgg(ParseState *pstate, char *aggname, Oid basetype,
         * Since "1" never evaluates as null, we currently have no need of
         * the "usenulls" flag, but it should be kept around; in fact, we should
         * extend the pg_aggregate table to let usenulls be specified as an
-        * attribute of user-defined aggregates.
+        * attribute of user-defined aggregates.  In the meantime, usenulls
+        * is just always set to "false".
         */
 
        aggform = (Form_pg_aggregate) GETSTRUCT(theAggTuple);
@@ -264,14 +238,8 @@ ParseAgg(ParseState *pstate, char *aggname, Oid basetype,
        aggref->aggtype = fintype;
        aggref->target = lfirst(args);
        aggref->usenulls = usenulls;
-
-       /*
-        * We should store agg_star and agg_distinct into the Aggref node,
-        * and let downstream processing deal with them.  Currently, agg_star
-        * is ignored and agg_distinct is not implemented...
-        */
-       if (agg_distinct)
-               elog(ERROR, "aggregate(DISTINCT ...) is not implemented yet");
+       aggref->aggstar = agg_star;
+       aggref->aggdistinct = agg_distinct;
 
        pstate->p_hasAggs = true;
 
index b62559c..47fd957 100644 (file)
@@ -3,7 +3,7 @@
  *                       out of it's tuple
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.34 1999/12/06 02:37:17 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.35 1999/12/13 01:27:01 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -1352,9 +1352,13 @@ get_rule_expr(Node *node, deparse_context *context)
                        {
                                Aggref     *aggref = (Aggref *) node;
 
-                               appendStringInfo(buf, "%s(",
-                                                                quote_identifier(aggref->aggname));
-                               get_rule_expr(aggref->target, context);
+                               appendStringInfo(buf, "%s(%s",
+                                                                quote_identifier(aggref->aggname),
+                                                                aggref->aggdistinct ? "DISTINCT " : "");
+                               if (aggref->aggstar)
+                                       appendStringInfo(buf, "*");
+                               else
+                                       get_rule_expr(aggref->target, context);
                                appendStringInfo(buf, ")");
                        }
                        break;
index 5297fde..6e9a23f 100644 (file)
@@ -3,8 +3,8 @@
  * tuplesort.c
  *       Generalized tuple sorting routines.
  *
- * This module handles sorting of either heap tuples or index tuples
- * (and could fairly easily support other kinds of sortable objects,
+ * This module handles sorting of heap tuples, index tuples, or single
+ * Datums (and could easily support other kinds of sortable objects,
  * if necessary).  It works efficiently for both small and large amounts
  * of data.  Small amounts are sorted in-memory using qsort().  Large
  * amounts are sorted using temporary files and a standard external sort
@@ -77,7 +77,7 @@
  * Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/sort/tuplesort.c,v 1.2 1999/10/30 17:27:15 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/sort/tuplesort.c,v 1.3 1999/12/13 01:27:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -87,7 +87,9 @@
 #include "access/heapam.h"
 #include "access/nbtree.h"
 #include "miscadmin.h"
+#include "parser/parse_type.h"
 #include "utils/logtape.h"
+#include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
 
 /*
@@ -251,6 +253,17 @@ struct Tuplesortstate
         */
        Relation        indexRel;
        bool            enforceUnique;  /* complain if we find duplicate tuples */
+
+       /*
+        * These variables are specific to the Datum case; they are set
+        * by tuplesort_begin_datum and used only by the DatumTuple routines.
+        */
+       Oid                     datumType;
+       Oid                     sortOperator;
+       FmgrInfo        sortOpFn;               /* cached lookup data for sortOperator */
+       /* we need typelen and byval in order to know how to copy the Datums. */
+       int                     datumTypeLen;
+       bool            datumTypeByVal;
 };
 
 #define COMPARETUP(state,a,b)  ((*(state)->comparetup) (state, a, b))
@@ -321,7 +334,22 @@ struct Tuplesortstate
  *--------------------
  */
 
+/*
+ * For sorting single Datums, we build "pseudo tuples" that just carry
+ * the datum's value and null flag.  For pass-by-reference data types,
+ * the actual data value appears after the DatumTupleHeader (MAXALIGNed,
+ * of course), and the value field in the header is just a pointer to it.
+ */
+
+typedef struct
+{
+       Datum           val;
+       bool            isNull;
+} DatumTuple;
+
+
 static Tuplesortstate *tuplesort_begin_common(bool randomAccess);
+static void puttuple_common(Tuplesortstate *state, void *tuple);
 static void inittapes(Tuplesortstate *state);
 static void selectnewtape(Tuplesortstate *state);
 static void mergeruns(Tuplesortstate *state);
@@ -349,6 +377,13 @@ static void writetup_index(Tuplesortstate *state, int tapenum, void *tup);
 static void *readtup_index(Tuplesortstate *state, int tapenum,
                                                   unsigned int len);
 static unsigned int tuplesize_index(Tuplesortstate *state, void *tup);
+static int comparetup_datum(Tuplesortstate *state,
+                                                       const void *a, const void *b);
+static void *copytup_datum(Tuplesortstate *state, void *tup);
+static void writetup_datum(Tuplesortstate *state, int tapenum, void *tup);
+static void *readtup_datum(Tuplesortstate *state, int tapenum,
+                                                  unsigned int len);
+static unsigned int tuplesize_datum(Tuplesortstate *state, void *tup);
 
 /*
  * Since qsort(3) will not pass any context info to qsort_comparetup(),
@@ -369,6 +404,7 @@ static Tuplesortstate *qsort_tuplesortstate;
  * have been supplied.  After performsort, retrieve the tuples in sorted
  * order by calling tuplesort_gettuple until it returns NULL.  (If random
  * access was requested, rescan, markpos, and restorepos can also be called.)
+ * For Datum sorts, putdatum/getdatum are used instead of puttuple/gettuple.
  * Call tuplesort_end to terminate the operation and release memory/disk space.
  */
 
@@ -444,6 +480,32 @@ tuplesort_begin_index(Relation indexRel,
        return state;
 }
 
+Tuplesortstate *
+tuplesort_begin_datum(Oid datumType,
+                                         Oid sortOperator,
+                                         bool randomAccess)
+{
+       Tuplesortstate *state = tuplesort_begin_common(randomAccess);
+       Type                    typeInfo;
+
+       state->comparetup = comparetup_datum;
+       state->copytup = copytup_datum;
+       state->writetup = writetup_datum;
+       state->readtup = readtup_datum;
+       state->tuplesize = tuplesize_datum;
+
+       state->datumType = datumType;
+       state->sortOperator = sortOperator;
+       /* lookup the function that implements the sort operator */
+       fmgr_info(get_opcode(sortOperator), &state->sortOpFn);
+       /* lookup necessary attributes of the datum type */
+       typeInfo = typeidType(datumType);
+       state->datumTypeLen = typeLen(typeInfo);
+       state->datumTypeByVal = typeByVal(typeInfo);
+
+       return state;
+}
+
 /*
  * tuplesort_end
  *
@@ -476,9 +538,60 @@ tuplesort_puttuple(Tuplesortstate *state, void *tuple)
 {
        /*
         * Copy the given tuple into memory we control, and decrease availMem.
+        * Then call the code shared with the Datum case.
         */
        tuple = COPYTUP(state, tuple);
 
+       puttuple_common(state, tuple);
+}
+
+/*
+ * Accept one Datum while collecting input data for sort.
+ *
+ * If the Datum is pass-by-ref type, the value will be copied.
+ */
+void
+tuplesort_putdatum(Tuplesortstate *state, Datum val, bool isNull)
+{
+       DatumTuple         *tuple;
+
+       /*
+        * Build pseudo-tuple carrying the datum, and decrease availMem.
+        */
+       if (isNull || state->datumTypeByVal)
+       {
+               USEMEM(state, sizeof(DatumTuple));
+               tuple = (DatumTuple *) palloc(sizeof(DatumTuple));
+               tuple->val = val;
+               tuple->isNull = isNull;
+       }
+       else
+       {
+               int             datalen = state->datumTypeLen;
+               int             tuplelen;
+               char   *newVal;
+
+               if (datalen == -1)              /* variable length type? */
+                       datalen = VARSIZE((struct varlena *) DatumGetPointer(val));
+               tuplelen = datalen + MAXALIGN(sizeof(DatumTuple));
+               USEMEM(state, tuplelen);
+               newVal = (char *) palloc(tuplelen);
+               tuple = (DatumTuple *) newVal;
+               newVal += MAXALIGN(sizeof(DatumTuple));
+               memcpy(newVal, DatumGetPointer(val), datalen);
+               tuple->val = PointerGetDatum(newVal);
+               tuple->isNull = false;
+       }
+
+       puttuple_common(state, (void *) tuple);
+}
+
+/*
+ * Shared code for tuple and datum cases.
+ */
+static void
+puttuple_common(Tuplesortstate *state, void *tuple)
+{
        switch (state->status)
        {
                case TSS_INITIAL:
@@ -754,6 +867,50 @@ tuplesort_gettuple(Tuplesortstate *state, bool forward,
 }
 
 /*
+ * Fetch the next Datum in either forward or back direction.
+ * Returns FALSE if no more datums.
+ *
+ * If the Datum is pass-by-ref type, the returned value is freshly palloc'd
+ * and is now owned by the caller.
+ */
+bool
+tuplesort_getdatum(Tuplesortstate *state, bool forward,
+                                  Datum *val, bool *isNull)
+{
+       DatumTuple         *tuple;
+       bool                    should_free;
+
+       tuple = (DatumTuple *) tuplesort_gettuple(state, forward, &should_free);
+
+       if (tuple == NULL)
+               return false;
+
+       if (tuple->isNull || state->datumTypeByVal)
+       {
+               *val = tuple->val;
+               *isNull = tuple->isNull;
+       }
+       else
+       {
+               int             datalen = state->datumTypeLen;
+               char   *newVal;
+
+               if (datalen == -1)              /* variable length type? */
+                       datalen = VARSIZE((struct varlena *) DatumGetPointer(tuple->val));
+               newVal = (char *) palloc(datalen);
+               memcpy(newVal, DatumGetPointer(tuple->val), datalen);
+               *val = PointerGetDatum(newVal);
+               *isNull = false;
+       }
+
+       if (should_free)
+               pfree(tuple);
+
+       return true;
+}
+
+
+/*
  * inittapes - initialize for tape sorting.
  *
  * This is called only if we have found we don't have room to sort in memory.
@@ -1695,3 +1852,103 @@ tuplesize_index(Tuplesortstate *state, void *tup)
 
        return tuplen;
 }
+
+
+/*
+ * Routines specialized for DatumTuple case
+ */
+
+static int
+comparetup_datum(Tuplesortstate *state, const void *a, const void *b)
+{
+       DatumTuple *ltup = (DatumTuple *) a;
+       DatumTuple *rtup = (DatumTuple *) b;
+
+       if (ltup->isNull)
+       {
+               if (!rtup->isNull)
+                       return 1;                       /* NULL sorts after non-NULL */
+               return 0;
+       }
+       else if (rtup->isNull)
+               return -1;
+       else
+       {
+               int             result;
+
+               if (!(result = - (int) (*fmgr_faddr(&state->sortOpFn)) (ltup->val,
+                                                                                                                               rtup->val)))
+                       result = (int) (*fmgr_faddr(&state->sortOpFn)) (rtup->val,
+                                                                                                                       ltup->val);
+               return result;
+       }
+}
+
+static void *
+copytup_datum(Tuplesortstate *state, void *tup)
+{
+       /* Not currently needed */
+       elog(ERROR, "copytup_datum() should not be called");
+       return NULL;
+}
+
+static void
+writetup_datum(Tuplesortstate *state, int tapenum, void *tup)
+{
+       DatumTuple         *tuple = (DatumTuple *) tup;
+       unsigned int    tuplen = tuplesize_datum(state, tup);
+       unsigned int    writtenlen = tuplen + sizeof(unsigned int);
+
+       LogicalTapeWrite(state->tapeset, tapenum,
+                                        (void*) &writtenlen, sizeof(writtenlen));
+       LogicalTapeWrite(state->tapeset, tapenum,
+                                        (void*) tuple, tuplen);
+       if (state->randomAccess)        /* need trailing length word? */
+               LogicalTapeWrite(state->tapeset, tapenum,
+                                                (void*) &writtenlen, sizeof(writtenlen));
+
+       FREEMEM(state, tuplen);
+       pfree(tuple);
+}
+
+static void *
+readtup_datum(Tuplesortstate *state, int tapenum, unsigned int len)
+{
+       unsigned int    tuplen = len - sizeof(unsigned int);
+       DatumTuple         *tuple = (DatumTuple *) palloc(tuplen);
+
+       USEMEM(state, tuplen);
+       if (LogicalTapeRead(state->tapeset, tapenum, (void *) tuple,
+                                               tuplen) != tuplen)
+               elog(ERROR, "tuplesort: unexpected end of data");
+       if (state->randomAccess)        /* need trailing length word? */
+               if (LogicalTapeRead(state->tapeset, tapenum, (void *) &tuplen,
+                                                       sizeof(tuplen)) != sizeof(tuplen))
+                       elog(ERROR, "tuplesort: unexpected end of data");
+
+       if (!tuple->isNull && !state->datumTypeByVal)
+               tuple->val = PointerGetDatum(((char *) tuple) +
+                                                                        MAXALIGN(sizeof(DatumTuple)));
+       return (void *) tuple;
+}
+
+static unsigned int
+tuplesize_datum(Tuplesortstate *state, void *tup)
+{
+       DatumTuple         *tuple = (DatumTuple *) tup;
+
+       if (tuple->isNull || state->datumTypeByVal)
+       {
+               return (unsigned int) sizeof(DatumTuple);
+       }
+       else
+       {
+               int             datalen = state->datumTypeLen;
+               int             tuplelen;
+
+               if (datalen == -1)              /* variable length type? */
+                       datalen = VARSIZE((struct varlena *) DatumGetPointer(tuple->val));
+               tuplelen = datalen + MAXALIGN(sizeof(DatumTuple));
+               return (unsigned int) tuplelen;
+       }
+}
index 728c62b..62244f8 100644 (file)
@@ -36,7 +36,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.4 1999/11/24 16:52:48 momjian Exp $
+ * $Id: catversion.h,v 1.5 1999/12/13 01:27:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,6 @@
  * catalog changes on the same day...)
  */
 
-#define CATALOG_VERSION_NO     199911241
+#define CATALOG_VERSION_NO     199912121
 
 #endif
index 2d585bd..d3fb8f7 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.37 1999/11/15 02:00:15 tgl Exp $
+ * $Id: primnodes.h,v 1.38 1999/12/13 01:27:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -297,10 +297,12 @@ typedef struct Iter
 /* ----------------
  * Aggref
  *             aggname                 - name of the aggregate
- *             basetype                - base type Oid of the aggregate
+ *             basetype                - base type Oid of the aggregate (ie, input type)
  *             aggtype                 - type Oid of final result of the aggregate
  *             target                  - attribute or expression we are aggregating on
  *             usenulls                - TRUE to accept null values as inputs
+ *             aggstar                 - TRUE if argument was really '*'
+ *             aggdistinct             - TRUE if arguments were labeled DISTINCT
  *             aggno                   - workspace for nodeAgg.c executor
  * ----------------
  */
@@ -312,6 +314,8 @@ typedef struct Aggref
        Oid                     aggtype;
        Node       *target;
        bool            usenulls;
+       bool            aggstar;
+       bool            aggdistinct;
        int                     aggno;
 } Aggref;
 
index 829bf43..4cd2e48 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: clauses.h,v 1.31 1999/12/09 05:58:55 tgl Exp $
+ * $Id: clauses.h,v 1.32 1999/12/13 01:27:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@ extern Expr *make_ands_explicit(List *andclauses);
 extern List *make_ands_implicit(Expr *clause);
 
 extern List *pull_constant_clauses(List *quals, List **constantQual);
+extern bool contain_agg_clause(Node *clause);
 extern List *pull_agg_clause(Node *clause);
 extern void check_subplans_for_ungrouped_vars(Node *clause,
                                                                                          Query *query,
index 7c5a320..4f775f7 100644 (file)
@@ -3,8 +3,8 @@
  * tuplesort.h
  *       Generalized tuple sorting routines.
  *
- * This module handles sorting of either heap tuples or index tuples
- * (and could fairly easily support other kinds of sortable objects,
+ * This module handles sorting of heap tuples, index tuples, or single
+ * Datums (and could easily support other kinds of sortable objects,
  * if necessary).  It works efficiently for both small and large amounts
  * of data.  Small amounts are sorted in-memory using qsort().  Large
  * amounts are sorted using temporary files and a standard external sort
@@ -12,7 +12,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: tuplesort.h,v 1.1 1999/10/17 22:15:09 tgl Exp $
+ * $Id: tuplesort.h,v 1.2 1999/12/13 01:27:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,6 +34,7 @@ typedef struct Tuplesortstate Tuplesortstate;
  * code: one for sorting HeapTuples and one for sorting IndexTuples.
  * They differ primarily in the way that the sort key information is
  * supplied.
+ * Yet a third slightly different interface supports sorting bare Datums.
  */
 
 extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc,
@@ -42,9 +43,15 @@ extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc,
 extern Tuplesortstate *tuplesort_begin_index(Relation indexRel,
                                                                                         bool enforceUnique,
                                                                                         bool randomAccess);
+extern Tuplesortstate *tuplesort_begin_datum(Oid datumType,
+                                                                                        Oid sortOperator,
+                                                                                        bool randomAccess);
 
 extern void tuplesort_puttuple(Tuplesortstate *state, void *tuple);
 
+extern void tuplesort_putdatum(Tuplesortstate *state, Datum val,
+                                                          bool isNull);
+
 extern void tuplesort_performsort(Tuplesortstate *state);
 
 extern void *tuplesort_gettuple(Tuplesortstate *state, bool forward,
@@ -54,11 +61,15 @@ extern void *tuplesort_gettuple(Tuplesortstate *state, bool forward,
 #define tuplesort_getindextuple(state, forward, should_free) \
        ((IndexTuple) tuplesort_gettuple(state, forward, should_free))
 
+extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward,
+                                                          Datum *val, bool *isNull);
+
 extern void tuplesort_end(Tuplesortstate *state);
 
 /*
  * These routines may only be called if randomAccess was specified 'true'.
- * Backwards scan in gettuple is likewise only allowed if randomAccess.
+ * Likewise, backwards scan in gettuple/getdatum is only allowed if
+ * randomAccess was specified.
  */
 
 extern void tuplesort_rescan(Tuplesortstate *state);
index 84958f6..5dac616 100644 (file)
@@ -76,6 +76,42 @@ cnt_1000
     1000
 (1 row)
 
+QUERY: SELECT count(DISTINCT four) AS cnt_4 FROM onek;
+cnt_4
+-----
+    4
+(1 row)
+
+QUERY: select ten, count(*), sum(four) from onek group by ten;
+ten|count|sum
+---+-----+---
+  0|  100|100
+  1|  100|200
+  2|  100|100
+  3|  100|200
+  4|  100|100
+  5|  100|200
+  6|  100|100
+  7|  100|200
+  8|  100|100
+  9|  100|200
+(10 rows)
+
+QUERY: select ten, count(four), sum(DISTINCT four) from onek group by ten;
+ten|count|sum
+---+-----+---
+  0|  100|  2
+  1|  100|  4
+  2|  100|  2
+  3|  100|  4
+  4|  100|  2
+  5|  100|  4
+  6|  100|  2
+  7|  100|  4
+  8|  100|  2
+  9|  100|  4
+(10 rows)
+
 QUERY: SELECT newavg(four) AS avg_1 FROM onek;
 avg_1
 -----
index 474ed0b..5938458 100644 (file)
@@ -1075,9 +1075,9 @@ pg_user           |SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecr
 pg_views          |SELECT c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.relname) AS definition FROM pg_class c WHERE (c.relhasrules AND (EXISTS (SELECT r.rulename FROM pg_rewrite r WHERE ((r.ev_class = c.oid) AND (r.ev_type = '1'::"char")))));                                                                                                                               
 rtest_v1          |SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;                                                                                                                                                                                                                                                                                                                                                       
 rtest_vcomp       |SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);                                                                                                                                                                                                                                                                                      
-rtest_vview1      |SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(1) AS count FROM rtest_view2 y WHERE (y.a = x.a)));                                                                                                                                                                                                                                                                                    
+rtest_vview1      |SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(*) AS count FROM rtest_view2 y WHERE (y.a = x.a)));                                                                                                                                                                                                                                                                                    
 rtest_vview2      |SELECT rtest_view1.a, rtest_view1.b FROM rtest_view1 WHERE rtest_view1.v;                                                                                                                                                                                                                                                                                                                          
-rtest_vview3      |SELECT x.a, x.b FROM rtest_vview2 x WHERE (0 < (SELECT count(1) AS count FROM rtest_view2 y WHERE (y.a = x.a)));                                                                                                                                                                                                                                                                                   
+rtest_vview3      |SELECT x.a, x.b FROM rtest_vview2 x WHERE (0 < (SELECT count(*) AS count FROM rtest_view2 y WHERE (y.a = x.a)));                                                                                                                                                                                                                                                                                   
 rtest_vview4      |SELECT x.a, x.b, count(y.a) AS refcount FROM rtest_view1 x, rtest_view2 y WHERE (x.a = y.a) GROUP BY x.a, x.b;                                                                                                                                                                                                                                                                                     
 rtest_vview5      |SELECT rtest_view1.a, rtest_view1.b, rtest_viewfunc1(rtest_view1.a) AS refcount FROM rtest_view1;                                                                                                                                                                                                                                                                                                  
 shoe              |SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, (sh.slminlen * un.un_fact) AS slminlen_cm, sh.slmaxlen, (sh.slmaxlen * un.un_fact) AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE (sh.slunit = un.un_name);                                                                                                                                                                  
index 1fc0996..03ea7de 100644 (file)
@@ -30,6 +30,12 @@ SELECT max(student.gpa) AS max_3_7 FROM student;
 
 SELECT count(four) AS cnt_1000 FROM onek;
 
+SELECT count(DISTINCT four) AS cnt_4 FROM onek;
+
+select ten, count(*), sum(four) from onek group by ten;
+
+select ten, count(four), sum(DISTINCT four) from onek group by ten;
+
 
 SELECT newavg(four) AS avg_1 FROM onek;