mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-11 19:20:40 +08:00
aggregate(DISTINCT ...) works, per SQL spec.
Note this forces initdb because of change of Aggref node in stored rules.
This commit is contained in:
parent
efb36d2be8
commit
a8ae19ec3d
@ -3,15 +3,35 @@
|
||||
* nodeAgg.c
|
||||
* Routines to handle aggregate nodes.
|
||||
*
|
||||
* 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...)
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* NOTE
|
||||
* The implementation of Agg node has been reworked to handle legal
|
||||
* SQL aggregates. (Do not expect POSTQUEL semantics.) -- ay 2/95
|
||||
*
|
||||
* 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 $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -20,11 +40,15 @@
|
||||
|
||||
#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;
|
||||
@ -47,6 +74,18 @@ typedef struct AggStatePerAggData
|
||||
FmgrInfo xfn1;
|
||||
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
|
||||
*/
|
||||
@ -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;
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
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);
|
||||
|
||||
if (typLen == -1) /* variable length type? */
|
||||
typLen = VARSIZE((struct varlena *) DatumGetPointer(val));
|
||||
newVal = (char *) palloc(typLen);
|
||||
memcpy(newVal, DatumGetPointer(val), typLen);
|
||||
return PointerGetDatum(newVal);
|
||||
|
||||
/*
|
||||
* Initialize one aggregate for a new set of input values.
|
||||
*/
|
||||
static void
|
||||
initialize_aggregate (AggStatePerAgg peraggstate)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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];
|
||||
AggStatePerAgg peraggstate = &peragg[aggno];
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
@ -393,12 +394,36 @@ pull_constant_clauses(List *quals, List **constantQual)
|
||||
return restqual;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
@ -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,38 +28,11 @@ 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
|
||||
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
@ -753,6 +866,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.
|
||||
*
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
-----
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user