diff --git a/columnar/src/backend/columnar/Makefile b/columnar/src/backend/columnar/Makefile index f4acba64..bcd4112a 100644 --- a/columnar/src/backend/columnar/Makefile +++ b/columnar/src/backend/columnar/Makefile @@ -1,7 +1,7 @@ citus_subdir = src/backend/columnar citus_top_builddir = ../../.. safestringlib_srcdir = $(citus_abs_top_srcdir)/vendor/safestringlib -SUBDIRS = . safeclib vectorization vectorization/types +SUBDIRS = . safeclib vectorization vectorization/types vectorization/nodes SUBDIRS += ENSURE_SUBDIRS_EXIST := $(shell mkdir -p $(SUBDIRS)) OBJS += \ @@ -9,6 +9,11 @@ OBJS += \ MODULE_big = columnar PG_CPPFLAGS += -I$(libpq_srcdir) -I$(safestringlib_srcdir)/include + +ifdef COLUMNAR_O3 +PG_CFLAGS += -O3 +endif + EXTENSION = columnar template_sql_files = $(patsubst $(citus_abs_srcdir)/%,%,$(wildcard $(citus_abs_srcdir)/sql/*.sql)) diff --git a/columnar/src/backend/columnar/columnar.c b/columnar/src/backend/columnar/columnar.c index e8898db7..7e42b7aa 100644 --- a/columnar/src/backend/columnar/columnar.c +++ b/columnar/src/backend/columnar/columnar.c @@ -37,6 +37,8 @@ #define DEFAULT_COMPRESSION_TYPE COMPRESSION_PG_LZ #endif +static void columnar_guc_init(void); + int columnar_compression = DEFAULT_COMPRESSION_TYPE; int columnar_stripe_row_limit = DEFAULT_STRIPE_ROW_COUNT; int columnar_chunk_group_row_limit = DEFAULT_CHUNK_ROW_COUNT; @@ -64,13 +66,14 @@ static const struct config_enum_entry columnar_compression_options[] = void columnar_init(void) { - columnar_init_gucs(); + columnar_guc_init(); columnar_tableam_init(); + columnar_planner_init(); } -void -columnar_init_gucs() +static void +columnar_guc_init() { DefineCustomEnumVariable("columnar.compression", "Compression type for columnar.", diff --git a/columnar/src/backend/columnar/columnar.control b/columnar/src/backend/columnar/columnar.control index 45c4c6a2..aee7c034 100644 --- a/columnar/src/backend/columnar/columnar.control +++ b/columnar/src/backend/columnar/columnar.control @@ -1,4 +1,4 @@ comment = 'Hydra Columnar extension' -default_version = '11.1-8' +default_version = '11.1-9' module_pathname = '$libdir/columnar' relocatable = false diff --git a/columnar/src/backend/columnar/columnar_customscan.c b/columnar/src/backend/columnar/columnar_customscan.c index 9471913f..a4d7b4a4 100644 --- a/columnar/src/backend/columnar/columnar_customscan.c +++ b/columnar/src/backend/columnar/columnar_customscan.c @@ -75,11 +75,14 @@ typedef struct ColumnarScanState struct { bool vectorizationEnabled; + bool vectorizationAggregate; TupleTableSlot *scanVectorSlot; + TupleTableSlot *resultVectorSlot; uint32 vectorPendingRowNumber; uint32 vectorRowIndex; List *vectorizedQualList; List *constructedVectorizedQualList; + List *attrNeededList; } vectorization; /* Scan snapshot*/ @@ -232,6 +235,13 @@ static const struct config_enum_entry debug_level_options[] = { }; +const CustomScanMethods * +columnar_customscan_methods(void) +{ + return &ColumnarScanScanMethods; +} + + /* * columnar_customscan_init installs the hook required to intercept the postgres planner and * provide extra paths for columnar tables @@ -440,8 +450,6 @@ ColumnarSetRelPathlistHook(PlannerInfo *root, RelOptInfo *rel, Index rti, if (EnableColumnarCustomScan) { - ereport(DEBUG1, (errmsg("pathlist hook for columnar table am"))); - /* * When columnar custom scan is enabled (columnar.enable_custom_scan), * we only consider ColumnarScanPath's & IndexPath's. For this reason, @@ -1899,13 +1907,41 @@ ColumnarScan_BeginCustomScan(CustomScanState *cscanstate, EState *estate, int ef if (lthird(cscan->custom_exprs) != NIL) columnarScanState->vectorization.vectorizedQualList = lthird(cscan->custom_exprs); + ListCell *lc; + foreach(lc, cscan->custom_private) + { + Const *privateCustomData = lfirst(lc); + if (privateCustomData->consttype == CUSTOM_SCAN_VECTORIZED_AGGREGATE) + { + columnarScanState->vectorization.vectorizationAggregate = + privateCustomData->constvalue; + } + } + /* * Vectorization is enabled if global variable is set and there is at least one * filter which can be vectorized. */ columnarScanState->vectorization.vectorizationEnabled = columnar_enable_vectorization && - columnarScanState->vectorization.vectorizedQualList != NULL; + (columnarScanState->vectorization.vectorizedQualList != NULL || + columnarScanState->vectorization.vectorizationAggregate); + + if (columnarScanState->vectorization.vectorizationAggregate) + { + ScanState *node = (ScanState *) &columnarScanState->custom_scanstate.ss; + + if (node->ps.ps_ProjInfo) + { + columnarScanState->vectorization.resultVectorSlot = + CreateVectorTupleTableSlot(node->ps.ps_ProjInfo->pi_state.resultslot->tts_tupleDescriptor); + } + else + { + columnarScanState->vectorization.resultVectorSlot = + CreateVectorTupleTableSlot(node->ps.ps_ResultTupleDesc); + } + } if (columnarScanState->vectorization.vectorizationEnabled) { @@ -1916,6 +1952,13 @@ ColumnarScan_BeginCustomScan(CustomScanState *cscanstate, EState *estate, int ef columnarScanState->attrNeeded = ColumnarAttrNeeded(&cscanstate->ss, columnarScanState->vectorization.vectorizedQualList); + int bmsMember = -1; + while ((bmsMember = bms_next_member(columnarScanState->attrNeeded, bmsMember)) >= 0) + { + columnarScanState->vectorization.attrNeededList = + lappend_int(columnarScanState->vectorization.attrNeededList, bmsMember); + } + /* * If we have pending changes that need to be flushed (row_mask after update/delete) * or new stripe we need to to them here because sequential columnar scan @@ -1981,14 +2024,11 @@ ColumnarAttrNeeded(ScanState *ss, List *customList) if (var->varattno == 0 ) { - elog(DEBUG1, "Need attribute: all"); - /* all attributes are required, we don't need to add more so break*/ attr_needed = bms_add_range(attr_needed, 0, natts - 1); break; } - elog(DEBUG1, "Need attribute: %d", var->varattno); attr_needed = bms_add_member(attr_needed, var->varattno - 1); } @@ -2140,6 +2180,15 @@ CustomExecScan(ColumnarScanState *columnarScanState, * storage allocated in the previous tuple cycle. */ ResetExprContext(econtext); + + uint resultVectorTupleSlotIdx = 0; + + if (columnarScanState->vectorization.vectorizationAggregate) + { + ExecClearTuple(columnarScanState->vectorization.resultVectorSlot); + CleanupVectorSlot((VectorTupleTableSlot *) + columnarScanState->vectorization.resultVectorSlot); + } /* * get a tuple from the access method. Loop until we obtain a tuple that @@ -2149,6 +2198,16 @@ CustomExecScan(ColumnarScanState *columnarScanState, { TupleTableSlot *slot = NULL; + if (columnarScanState->vectorization.vectorizationEnabled && + columnarScanState->vectorization.vectorPendingRowNumber == 0 && + resultVectorTupleSlotIdx > 0) + { + ((VectorTupleTableSlot *) columnarScanState->vectorization.resultVectorSlot)->dimension = resultVectorTupleSlotIdx; + ExecStoreVirtualTuple(columnarScanState->vectorization.resultVectorSlot); + resultVectorTupleSlotIdx = 0; + return columnarScanState->vectorization.resultVectorSlot; + } + /* * No vectorizaion in place so fetch slot by slot. */ @@ -2156,6 +2215,8 @@ CustomExecScan(ColumnarScanState *columnarScanState, { slot = ExecScanFetch(node, accessMtd, recheckMtd); + resultVectorTupleSlotIdx = 0; + /* * if the slot returned by the accessMtd contains NULL, then it means * there is nothing more to scan so we just return an empty slot, @@ -2193,27 +2254,37 @@ CustomExecScan(ColumnarScanState *columnarScanState, return slot; } - if (columnarScanState->vectorization.constructedVectorizedQualList == NULL) + if (columnarScanState->vectorization.vectorizedQualList != NULL) { - columnarScanState->vectorization.constructedVectorizedQualList = - ConstructVectorizedQualList(slot, columnarScanState->vectorization.vectorizedQualList); - } - VectorTupleTableSlot *vectorSlot = (VectorTupleTableSlot *) slot; + if (columnarScanState->vectorization.constructedVectorizedQualList == NULL) + { + columnarScanState->vectorization.constructedVectorizedQualList = + ConstructVectorizedQualList(slot, columnarScanState->vectorization.vectorizedQualList); + } + + VectorTupleTableSlot *vectorSlot = (VectorTupleTableSlot *) slot; - bool *resultQual = - ExecuteVectorizedQual(slot, - columnarScanState->vectorization.constructedVectorizedQualList, - AND_EXPR); - - int i; - for (i = 0; i < vectorSlot->dimension; i++) + bool *resultQual = + ExecuteVectorizedQual(slot, + columnarScanState->vectorization.constructedVectorizedQualList, + AND_EXPR); + + memcpy(vectorSlot->keep, resultQual, COLUMNAR_VECTOR_COLUMN_SIZE); + } + /* + * No qual, no vectorized qual, no projection but we need to return vector + * for vectorized aggregate + */ + else if (!qual && !projInfo && columnarScanState->vectorization.vectorizationAggregate) { - if (!resultQual[i]) - vectorSlot->skip[i] = true; + columnarScanState->vectorization.vectorPendingRowNumber = 0; + return slot; } } + uint64 rowNumber = 0; + /* * Get next tuple from vector tuple slot if vectorization execution * is enabled. @@ -2228,14 +2299,18 @@ CustomExecScan(ColumnarScanState *columnarScanState, while (columnarScanState->vectorization.vectorPendingRowNumber != 0 && rowFound == false) { - if (!vectorSlot->skip[columnarScanState->vectorization.vectorRowIndex]) + if (vectorSlot->keep[columnarScanState->vectorization.vectorRowIndex]) { slot = columnarScanState->custom_scanstate.ss.ss_ScanTupleSlot; ExecClearTuple(slot); - extractTupleFromVectorSlot(slot, + ExtractTupleFromVectorSlot(slot, vectorSlot, columnarScanState->vectorization.vectorRowIndex, - columnarScanState->attrNeeded); + columnarScanState->vectorization.attrNeededList); + + rowNumber = vectorSlot->rowNumber[columnarScanState->vectorization.vectorRowIndex]; + if (!columnarScanState->vectorization.vectorizationAggregate) + slot->tts_tid = row_number_to_tid(rowNumber); rowFound = true; } columnarScanState->vectorization.vectorPendingRowNumber--; @@ -2269,13 +2344,21 @@ CustomExecScan(ColumnarScanState *columnarScanState, * Form a projection tuple, store it in the result tuple slot * and return it. */ - return ExecProject(projInfo); + slot = ExecProject(projInfo); } + + if (columnarScanState->vectorization.vectorizationAggregate) + { + WriteTupleToVectorSlot(slot, + (VectorTupleTableSlot *) columnarScanState->vectorization.resultVectorSlot, + resultVectorTupleSlotIdx); + ((VectorTupleTableSlot *) + columnarScanState->vectorization.resultVectorSlot)->rowNumber[resultVectorTupleSlotIdx] = rowNumber; + resultVectorTupleSlotIdx++; + continue; + } else { - /* - * Here, we aren't projecting, so just return scan tuple. - */ return slot; } } @@ -2337,10 +2420,10 @@ ColumnarScanNext(ColumnarScanState *columnarScanState) { VectorTupleTableSlot *vectorSlot = (VectorTupleTableSlot *) slot; int attrIndex = -1; - while ((attrIndex = bms_next_member(columnarScanState->attrNeeded, attrIndex)) >= 0) + foreach_int(attrIndex, columnarScanState->vectorization.attrNeededList) { VectorColumn *column = (VectorColumn *) vectorSlot->tts.tts_values[attrIndex]; - memset(column->isnull, 1, COLUMNAR_VECTOR_COLUMN_SIZE); + memset(column->isnull, true, COLUMNAR_VECTOR_COLUMN_SIZE); column->dimension = 0; } vectorSlot->dimension = 0; @@ -2482,7 +2565,8 @@ ColumnarScan_ExplainCustomScan(CustomScanState *node, List *ancestors, } } - if (columnarScanState->vectorization.vectorizationEnabled) + if (columnarScanState->vectorization.vectorizationEnabled && + columnarScanState->vectorization.vectorizedQualList != NULL) { const char *vectorizedWhereClauses = ColumnarPushdownClausesStr( context, columnarScanState->vectorization.vectorizedQualList); diff --git a/columnar/src/backend/columnar/columnar_planner_hook.c b/columnar/src/backend/columnar/columnar_planner_hook.c new file mode 100644 index 00000000..810209c2 --- /dev/null +++ b/columnar/src/backend/columnar/columnar_planner_hook.c @@ -0,0 +1,327 @@ +/*------------------------------------------------------------------------- + * + * columnar_planner_hook.c + * + * Copyright (c) Hydra, Inc. + * + * Modify top plan and change aggregate function to provided ones that can execute on + * column vector. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" + +#include "access/amapi.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_am.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "commands/defrem.h" +#include "optimizer/cost.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planner.h" +#include "optimizer/restrictinfo.h" +#include "tcop/utility.h" +#include "parser/parse_oper.h" +#include "parser/parse_func.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/selfuncs.h" +#include "utils/syscache.h" +#include "utils/spccache.h" + +#include "columnar/columnar.h" +#include "columnar/columnar_customscan.h" +#include "columnar/vectorization/columnar_vector_execution.h" +#include "columnar/vectorization/nodes/columnar_aggregator_node.h" + +#include "columnar/utils/listutils.h" + +static planner_hook_type PreviousPlannerHook = NULL; + +static PlannedStmt * ColumnarPlannerHook(Query *parse, const char *query_string, + int cursorOptions, ParamListInfo boundParams); + +#if PG_VERSION_NUM >= PG_VERSION_14 +static Plan * PlanTreeMutator(Plan *node, void *context); + +typedef struct PlanTreeMutatorContext +{ + bool vectorizedAggregation; +} PlanTreeMutatorContext; + + +#define FLATCOPY(newnode, node, nodetype) \ + ( (newnode) = (nodetype *) palloc(sizeof(nodetype)), \ + memcpy((newnode), (node), sizeof(nodetype)) ) + +static Node * +AggRefArgsExpressionMutator(Node *node, void *context) +{ + if (node == NULL) + return false; + + Node *previousNode = (Node *) context; + + if (IsA(node, OpExpr) || IsA(node, DistinctExpr) || IsA(node, NullIfExpr) ) + { + OpExpr *opExprNode = (OpExpr *) node; + + Form_pg_operator operatorForm; + HeapTuple operatorTuple; + + if (list_length(opExprNode->args) != 2) + { + elog(ERROR, "Aggregation vectorizaion works only on two arguments."); + return false; + } + + if (CheckOpExprArgumentRules(opExprNode->args)) + { + elog(ERROR, "Unsupported aggregate argument combination."); + return false; + } + + operatorTuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(opExprNode->opno)); + operatorForm = (Form_pg_operator) GETSTRUCT(operatorTuple); + Oid procedureOid = operatorForm->oprcode; + ReleaseSysCache(operatorTuple); + + Oid vectorizedProcedureOid; + if (!GetVectorizedProcedureOid(procedureOid, &vectorizedProcedureOid)) + { + elog(ERROR, "Vectorized aggregate not found."); + } + + opExprNode->opfuncid = vectorizedProcedureOid; + + return (Node *) opExprNode; + } + + /* This should handle aggregates that use only const as argument */ + if (previousNode != NULL && IsA(previousNode, TargetEntry) && IsA(node, Const)) + { + elog(ERROR, "Vectorized Aggregates accepts only non-const values."); + return false; + } + + return expression_tree_mutator(node, AggRefArgsExpressionMutator, (void *) node); +} + +static Node * +ExpressionMutator(Node *node, void *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Aggref)) + { + Aggref *oldAggRefNode = (Aggref *) node; + Aggref *newAggRefNode = copyObject(oldAggRefNode); + + if (oldAggRefNode->aggdistinct) + { + elog(ERROR, "Vectorized aggregate with DISTINCT not supported."); + } + + newAggRefNode->args = (List *) + expression_tree_mutator((Node *) oldAggRefNode->args, AggRefArgsExpressionMutator, NULL); + + Oid vectorizedProcedureOid = 0; + if (!GetVectorizedProcedureOid(newAggRefNode->aggfnoid, &vectorizedProcedureOid)) + { + elog(ERROR, "Vectorized aggregate not found."); + } + + newAggRefNode->aggfnoid = vectorizedProcedureOid; + + return (Node *) newAggRefNode; + } + + return expression_tree_mutator(node, ExpressionMutator, (void *) context); +} + + +static Plan * +PlanTreeMutator(Plan *node, void *context) +{ + if (node == NULL) + return NULL; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + switch (nodeTag(node)) + { + case T_CustomScan: + { + CustomScan *customScan = (CustomScan *) node; + + if (customScan->methods == columnar_customscan_methods()) + { + PlanTreeMutatorContext *planTreeContext = (PlanTreeMutatorContext *) context; + + Const * vectorizedAggregateExecution = makeNode(Const); + + vectorizedAggregateExecution->constbyval = true; + vectorizedAggregateExecution->consttype = CUSTOM_SCAN_VECTORIZED_AGGREGATE; + vectorizedAggregateExecution->constvalue = planTreeContext->vectorizedAggregation; + vectorizedAggregateExecution->constlen = sizeof(bool); + + customScan->custom_private = lappend(customScan->custom_private, vectorizedAggregateExecution); + } + + break; + } + + case T_Agg: + { + Agg *aggNode = (Agg *) node; + Agg *newAgg; + CustomScan *vectorizedAggNode; + + if (aggNode->plan.lefttree->type == T_CustomScan) + { + if (aggNode->aggstrategy == AGG_PLAIN) + { + vectorizedAggNode = columnar_create_aggregator_node(); + + FLATCOPY(newAgg, aggNode, Agg); + + newAgg->plan.targetlist = + (List *) expression_tree_mutator((Node *) newAgg->plan.targetlist, ExpressionMutator, NULL); + + + vectorizedAggNode->custom_plans = + lappend(vectorizedAggNode->custom_plans, newAgg); + vectorizedAggNode->scan.plan.targetlist = + CustomBuildTargetList(aggNode->plan.targetlist, INDEX_VAR); + vectorizedAggNode->custom_scan_tlist = newAgg->plan.targetlist; + + // Parallel agg node + Plan *vectorizedAggNodePlan = (Plan *) vectorizedAggNode; + vectorizedAggNodePlan->parallel_aware = aggNode->plan.lefttree->parallel_aware; + vectorizedAggNodePlan->startup_cost = aggNode->plan.startup_cost; + vectorizedAggNodePlan->total_cost = aggNode->plan.total_cost; + vectorizedAggNodePlan->plan_rows = aggNode->plan.plan_rows; + vectorizedAggNodePlan->plan_width = aggNode->plan.plan_width; + + + PlanTreeMutatorContext *planTreeContext = (PlanTreeMutatorContext *) context; + planTreeContext->vectorizedAggregation = true; + + PlanTreeMutator(node->lefttree, context); + PlanTreeMutator(node->righttree, context); + + vectorizedAggNode->scan.plan.lefttree = node->lefttree; + vectorizedAggNode->scan.plan.righttree = node->righttree; + + return (Plan *) vectorizedAggNode; + } + + return node; + } + + break; + } + default: + { + + break; + } + } + + node->lefttree = PlanTreeMutator(node->lefttree, context); + node->righttree = PlanTreeMutator(node->righttree, context); + + return node; +} +#endif + +static PlannedStmt * +ColumnarPlannerHook(Query *parse, + const char *query_string, + int cursorOptions, + ParamListInfo boundParams) +{ + PlannedStmt *stmt; +#if PG_VERSION_NUM >= PG_VERSION_14 + Plan *savedPlanTree; + List *savedSubplan; + MemoryContext saved_context; +#endif + + if (PreviousPlannerHook) + stmt = PreviousPlannerHook(parse, query_string, cursorOptions, boundParams); + else + stmt = standard_planner(parse, query_string, cursorOptions, boundParams); + +#if PG_VERSION_NUM >= PG_VERSION_14 + if (!columnar_enable_vectorization /* Vectorization should be enabled */ + || stmt->commandType != CMD_SELECT /* only SELECTS are supported */ + || list_length(stmt->rtable) != 1) /* JOINs are not yet supported */ + return stmt; + + + savedPlanTree = stmt->planTree; + savedSubplan = stmt->subplans; + + saved_context = CurrentMemoryContext; + + PG_TRY(); + { + List *subplans = NULL; + ListCell *cell; + + PlanTreeMutatorContext plainTreeContext; + plainTreeContext.vectorizedAggregation = 0; + + stmt->planTree = (Plan *) PlanTreeMutator(stmt->planTree, (void *) &plainTreeContext); + + foreach(cell, stmt->subplans) + { + PlanTreeMutatorContext subPlainTreeContext; + plainTreeContext.vectorizedAggregation = 0; + Plan *subplan = (Plan *) PlanTreeMutator(lfirst(cell), (void *) &subPlainTreeContext); + subplans = lappend(subplans, subplan); + } + + stmt->subplans = subplans; + } + PG_CATCH(); + { + ErrorData *edata; + MemoryContextSwitchTo(saved_context); + + edata = CopyErrorData(); + FlushErrorState(); + ereport(DEBUG1, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Query can't be vectorized. Falling back to original execution."), + errdetail("%s", edata->message))); + stmt->planTree = savedPlanTree; + stmt->subplans = savedSubplan; + } + + PG_END_TRY(); +#endif + + return stmt; +} + + +void columnar_planner_init(void) +{ + PreviousPlannerHook = planner_hook; + planner_hook = ColumnarPlannerHook; +#if PG_VERSION_NUM >= PG_VERSION_14 + columnar_register_aggregator_node(); +#endif +} \ No newline at end of file diff --git a/columnar/src/backend/columnar/columnar_reader.c b/columnar/src/backend/columnar/columnar_reader.c index 3f58f256..5593677b 100644 --- a/columnar/src/backend/columnar/columnar_reader.c +++ b/columnar/src/backend/columnar/columnar_reader.c @@ -2066,7 +2066,7 @@ ReadStripeNextVector(StripeReadState *stripeReadState, Datum *columnValues, continue; } - stripeReadState->currentRow += *newVectorSize; + stripeReadState->currentRow += stripeReadState->chunkGroupReadState->rowCount; return true; } @@ -2111,7 +2111,6 @@ ReadChunkGroupNextVector(ChunkGroupReadState *chunkGroupReadState, Datum *column if (checkLookupMask & checkColumnMask) { - (*chunkReadRows)++; chunkGroupReadState->currentRow++; continue; } diff --git a/columnar/src/backend/columnar/columnar_tableam.c b/columnar/src/backend/columnar/columnar_tableam.c index bb2e75b8..bdff27d8 100644 --- a/columnar/src/backend/columnar/columnar_tableam.c +++ b/columnar/src/backend/columnar/columnar_tableam.c @@ -385,7 +385,7 @@ columnar_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlo vectorTTS->dimension = newVectorSize; - memset(vectorTTS->skip, 0, newVectorSize); + memset(vectorTTS->keep, true, newVectorSize); ExecStoreVirtualTuple(slot); } @@ -1219,6 +1219,9 @@ TruncateAndCombineColumnarStripes(Relation rel, int elevel) startingStripeListPosition++; } + /* + * One stripe that is "full" so do nothing. + */ if (startingStripeListPosition == 0) { return false; @@ -1228,7 +1231,7 @@ TruncateAndCombineColumnarStripes(Relation rel, int elevel) * There is only one stripe that is candidate. Maybe we should vacuum * it if condition is met. */ - if (startingStripeListPosition == 1) + else if (startingStripeListPosition == 1) { /* Maybe we should vacuum only one stripe if count of * deleted rows is higher than 20 percent. diff --git a/columnar/src/backend/columnar/sql/columnar--11.1-8--11.1-9.sql b/columnar/src/backend/columnar/sql/columnar--11.1-8--11.1-9.sql new file mode 100644 index 00000000..bd94783e --- /dev/null +++ b/columnar/src/backend/columnar/sql/columnar--11.1-8--11.1-9.sql @@ -0,0 +1,68 @@ +-- columnar--11.1-8--11.1-9.sql + +-- count + +CREATE FUNCTION vemptycount(int8) RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE FUNCTION vanycount(int8, "any") RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; + +CREATE AGGREGATE vcount(*) (SFUNC = vemptycount, STYPE = int8, INITCOND="0"); +CREATE AGGREGATE vcount("any") (SFUNC = vanycount, STYPE = int8, INITCOND="0"); + +-- int2 / int4 + +CREATE FUNCTION vint2int4avg(internal) RETURNS numeric AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; + +-- int2 + +CREATE FUNCTION vint2sum(int8, int2) RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vsum(int2) (SFUNC = vint2sum, STYPE = int8, INITCOND="0"); + +CREATE FUNCTION vint2acc(internal, int2) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vavg(int2) (SFUNC = vint2acc, STYPE = internal, FINALFUNC = vint2int4avg, INITCOND = '{0,0}'); + +CREATE FUNCTION vint2larger(int2, int2) RETURNS int2 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmax(int2) (SFUNC = vint2larger, STYPE = int2, INITCOND="-32768"); + +CREATE FUNCTION vint2smaller(int2, int2) RETURNS int2 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmin(int2) (SFUNC = vint2smaller, STYPE = int2, INITCOND="32767"); + +-- int4 + +CREATE FUNCTION vint4sum(int8, int4) RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vsum(int4) (SFUNC = vint4sum, STYPE = int8, INITCOND="0"); + +CREATE FUNCTION vint4acc(internal, int4) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vavg(int4) (SFUNC = vint4acc, STYPE = internal, FINALFUNC = vint2int4avg, INITCOND = '{0,0}'); + +CREATE FUNCTION vint4larger(int4, int4) RETURNS int4 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmax(int4) (SFUNC = vint4larger, STYPE = int4, INITCOND="-2147483648"); + +CREATE FUNCTION vint4smaller(int4, int4) RETURNS int4 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmin(int4) (SFUNC = vint4smaller, STYPE = int4, INITCOND="2147483647"); + + +-- int8 + +CREATE FUNCTION vint8acc(internal, int8) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; + +CREATE FUNCTION vint8sum(internal) RETURNS numeric AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vsum(int8) (SFUNC = vint8acc, STYPE = internal, FINALFUNC = vint8sum, + SERIALFUNC = int8_avg_serialize, DESERIALFUNC = int8_avg_deserialize); + +CREATE FUNCTION vint8avg(internal) RETURNS numeric AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vavg(int8) (SFUNC = vint8acc, STYPE = internal, FINALFUNC = vint8avg, + SERIALFUNC = int8_avg_serialize, DESERIALFUNC = int8_avg_deserialize); + +CREATE FUNCTION vint8larger(int8, int8) RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmax(int8) (SFUNC = vint8larger, STYPE = int8, INITCOND="-9223372036854775808"); + +CREATE FUNCTION vint8smaller(int8, int8) RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmin(int8) (SFUNC = vint8smaller, STYPE = int8, INITCOND="9223372036854775807"); + +-- date + +CREATE FUNCTION vdatelarger(date, date) RETURNS date AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmax(date) (SFUNC = vdatelarger, STYPE = date, INITCOND="-infinity"); + +CREATE FUNCTION vdatesmaller(date, date) RETURNS date AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE AGGREGATE vmin(date) (SFUNC = vdatesmaller, STYPE = date, INITCOND="infinity"); diff --git a/columnar/src/backend/columnar/vectorization/columnar_vector_execution.c b/columnar/src/backend/columnar/vectorization/columnar_vector_execution.c index 24f7e256..b3666da8 100644 --- a/columnar/src/backend/columnar/vectorization/columnar_vector_execution.c +++ b/columnar/src/backend/columnar/vectorization/columnar_vector_execution.c @@ -32,8 +32,8 @@ * For now, vectorization only support "normal" clauses where * we compare tuple column against constant value. */ -static bool -checkOpExprArgumentRules(List *args) +bool +CheckOpExprArgumentRules(List *args) { ListCell *lcOpExprArgs; bool invalidArgument = false; @@ -77,6 +77,79 @@ checkOpExprArgumentRules(List *args) return invalidArgument; } +/* + * Get vectorized procedure OID. + */ +bool +GetVectorizedProcedureOid(Oid procedureOid, Oid *vectorizedProcedureOid) +{ + Form_pg_proc procedureForm; + HeapTuple procedureTuple; + + List *funcNames = NIL; + Oid *argtypes; + FuncDetailCode fdResult; + int i; + + bool retset; + Oid retype; + int nvargs; + Oid vatype; + Oid *true_oid_array; + + procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedureOid)); + procedureForm = (Form_pg_proc) GETSTRUCT(procedureTuple); + + int originalProcedureNameLen = strlen(NameStr(procedureForm->proname)); + + char * vectorizedProcedureName = + palloc0(sizeof(char) * originalProcedureNameLen + 2); + + vectorizedProcedureName[0] = 'v'; + + memcpy(vectorizedProcedureName + 1, + NameStr(procedureForm->proname), + originalProcedureNameLen); + + ReleaseSysCache(procedureTuple); + + funcNames = lappend(funcNames, makeString(vectorizedProcedureName)); + + argtypes = palloc(sizeof(Oid) * procedureForm->pronargs); + + for (i = 0; i < procedureForm->pronargs; i++) + argtypes[i] = procedureForm->proargtypes.values[i]; + +#if PG_VERSION_NUM >= PG_VERSION_14 + fdResult = func_get_detail(funcNames, NIL, NIL, + procedureForm->pronargs, argtypes, + false, true, false, + vectorizedProcedureOid, + &retype, &retset, + &nvargs, &vatype, + &true_oid_array, NULL); +#else + fdResult = func_get_detail(funcNames, + NIL, NIL, + procedureForm->pronargs, argtypes, + false, false, + vectorizedProcedureOid, &retype, + &retset, &nvargs, &vatype, + &true_oid_array, NULL); +#endif + + if ((fdResult == FUNCDETAIL_NOTFOUND || fdResult == FUNCDETAIL_MULTIPLE) || + !OidIsValid(*vectorizedProcedureOid) || + (procedureForm->pronargs != 0 && + memcmp(argtypes, true_oid_array, procedureForm->pronargs * sizeof(Oid)) != 0)) + { + return false; + } + + return true; + +} + List * CreateVectorizedExprList(List *exprList) { @@ -103,20 +176,8 @@ CreateVectorizedExprList(List *exprList) { OpExpr *opExprNode = (OpExpr *) node; - Form_pg_proc procedureForm; Form_pg_operator operatorForm; HeapTuple operatorTuple; - HeapTuple procedureTuple; - List *funcNames = NIL; - Oid *argtypes; - FuncDetailCode fdResult; - int i; - - bool retset; - Oid retype; - int nvargs; - Oid vatype; - Oid *true_oid_array; if (list_length(opExprNode->args) != 2) { @@ -128,7 +189,7 @@ CreateVectorizedExprList(List *exprList) * Let's inspect argument rules and break if they * don't match rules for vectorized execution. */ - if (checkOpExprArgumentRules(opExprNode->args)) + if (CheckOpExprArgumentRules(opExprNode->args)) { newQualList = lappend(newQualList, opExprNode); break; @@ -136,63 +197,18 @@ CreateVectorizedExprList(List *exprList) operatorTuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(opExprNode->opno)); operatorForm = (Form_pg_operator) GETSTRUCT(operatorTuple); - - Oid operatorOid = operatorForm->oprcode; - + Oid procedureOid = operatorForm->oprcode; ReleaseSysCache(operatorTuple); - procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(operatorOid)); - procedureForm = (Form_pg_proc) GETSTRUCT(procedureTuple); - - int originalProcedureNameLen = - strlen(NameStr(procedureForm->proname)); - - char * vectorizedProcedureName = - palloc0(sizeof(char) * originalProcedureNameLen + 2); - - vectorizedProcedureName[0] = 'v'; - - memcpy(vectorizedProcedureName + 1, - NameStr(procedureForm->proname), - originalProcedureNameLen); - - ReleaseSysCache(procedureTuple); - - funcNames = lappend(funcNames, makeString(vectorizedProcedureName)); - - argtypes = palloc(sizeof(Oid) * procedureForm->pronargs); - - for (i = 0; i < procedureForm->pronargs; i++) - argtypes[i] = procedureForm->proargtypes.values[i]; - - Oid vectorizedProcedureOperatorOid; - -#if PG_VERSION_NUM >= PG_VERSION_14 - fdResult = func_get_detail(funcNames, NIL, NIL, - procedureForm->pronargs, argtypes, - false, false, false, - &vectorizedProcedureOperatorOid, - &retype, &retset, - &nvargs, &vatype, - &true_oid_array, NULL); -#else - fdResult = func_get_detail(funcNames, - NIL, NIL, - procedureForm->pronargs, argtypes, - false, false, - &vectorizedProcedureOperatorOid, &retype, - &retset, &nvargs, &vatype, - &true_oid_array, NULL); -#endif - - if (fdResult == FUNCDETAIL_NOTFOUND) + Oid vectorizedOid; + if (!GetVectorizedProcedureOid(procedureOid, &vectorizedOid)) { newQualList = lappend(newQualList, opExprNode); break; } OpExpr *opExprNodeVector = copyObject(opExprNode); - opExprNodeVector->opfuncid = vectorizedProcedureOperatorOid; + opExprNodeVector->opfuncid = vectorizedOid; newQualList = lappend(newQualList, opExprNodeVector); break; @@ -279,7 +295,7 @@ ConstructVectorizedQualList(TupleTableSlot *slot, List *vectorizedQual) /* Initialize function call parameter structure too */ InitFunctionCallInfoData(*(newVectorQual->u.expr.fcInfo), newVectorQual->u.expr.fmgrInfo, - nargs, opExprNode->opcollid, NULL, NULL); + nargs, opExprNode->inputcollid, NULL, NULL); ListCell *lcOpExprArgs; foreach(lcOpExprArgs, opExprNode->args) @@ -351,12 +367,16 @@ ConstructVectorizedQualList(TupleTableSlot *slot, List *vectorizedQual) return vectorQualList; } +/* + * vectorizedOr / vectorizedAnd + */ + static void vectorizedAnd(bool *left, bool *right, int dimension) { for (int n = 0; n < dimension; n++) { - left[n] = left[n] && right[n]; + left[n] &= right[n]; } } @@ -365,8 +385,8 @@ vectorizedOr(bool *left, bool *right, int dimension) { for (int n = 0; n < dimension; n++) { - left[n] = left[n] || right[n]; - } + left[n] |= right[n]; + } } static bool * diff --git a/columnar/src/backend/columnar/vectorization/columnar_vector_types.c b/columnar/src/backend/columnar/vectorization/columnar_vector_types.c index 61046232..5379eda7 100644 --- a/columnar/src/backend/columnar/vectorization/columnar_vector_types.c +++ b/columnar/src/backend/columnar/vectorization/columnar_vector_types.c @@ -17,6 +17,8 @@ #include "columnar/columnar.h" #include "columnar/vectorization/columnar_vector_types.h" +#include "columnar/utils/listutils.h" + VectorColumn * BuildVectorColumn(int16 columnDimension, int16 columnTypeLen, bool columnIsVal, uint64 *rowNumber) @@ -54,7 +56,7 @@ CreateVectorTupleTableSlot(TupleDesc tupleDesc) vectorTTS = (VectorTupleTableSlot*) slot; /* All tuples should be skipped in initialization */ - memset(vectorTTS->skip, true, sizeof(vectorTTS->skip)); + memset(vectorTTS->keep, false, COLUMNAR_VECTOR_COLUMN_SIZE); for (i = 0; i < slotTupleDesc->natts; i++) { @@ -81,26 +83,89 @@ CreateVectorTupleTableSlot(TupleDesc tupleDesc) vectorTTS->tts.tts_isnull[i] = false; } + vectorTTS->tts.tts_nvalid = tupleDesc->natts; + return slot; } void -extractTupleFromVectorSlot(TupleTableSlot *out, VectorTupleTableSlot *vectorSlot, - int32 index, Bitmapset *attrNeeded) +ExtractTupleFromVectorSlot(TupleTableSlot *out, VectorTupleTableSlot *vectorSlot, + int32 index, List *attrNeededList) +{ + int attno; + foreach_int(attno, attrNeededList) + { + if (!out->tts_tupleDescriptor->attrs[attno].attisdropped) + { + VectorColumn *column = (VectorColumn *) vectorSlot->tts.tts_values[attno]; + int8 *rawColumRawData = (int8*) column->value + column->columnTypeLen * index; + out->tts_values[attno] = fetch_att(rawColumRawData, column->columnIsVal, column->columnTypeLen); + out->tts_isnull[attno] = column->isnull[index]; + } + } + + ExecStoreVirtualTuple(out); +} + +void +WriteTupleToVectorSlot(TupleTableSlot *in, VectorTupleTableSlot *vectorSlot, + int32 index) { - int bmsMember = -1; - while ((bmsMember = bms_next_member(attrNeeded, bmsMember)) >= 0) + TupleDesc tupDesc = in->tts_tupleDescriptor; + + int i; + + //vectorSlot->keep[index] = true; + + for (i = 0; i < tupDesc->natts; i++) { - VectorColumn *column = (VectorColumn *) vectorSlot->tts.tts_values[bmsMember]; + VectorColumn *column = (VectorColumn *) vectorSlot->tts.tts_values[i]; + + if (!in->tts_isnull[i]) + { + column->isnull[column->dimension] = false; - int8 *rawColumRawData = (int8*) column->value + column->columnTypeLen * index; + if (column->columnIsVal) + { + int8 *writeColumnRowPosition = (int8 *) column->value + column->columnTypeLen * index; - out->tts_values[bmsMember] = fetch_att(rawColumRawData, column->columnIsVal, column->columnTypeLen); - out->tts_isnull[bmsMember] = column->isnull[index]; + store_att_byval(writeColumnRowPosition, in->tts_values[i], column->columnTypeLen); + } + else + { + Pointer val = DatumGetPointer(in->tts_values[i]); + + Size data_length = VARSIZE_ANY(val); + + Datum *varLenTypeContainer = NULL; + + varLenTypeContainer = palloc0(sizeof(int8) * data_length); + memcpy(varLenTypeContainer, val, data_length); + + *(Datum *) ((int8 *) column->value + column->columnTypeLen * index) = + PointerGetDatum(varLenTypeContainer); + } + } + + column->dimension++; } +} - out->tts_tid = row_number_to_tid(vectorSlot->rowNumber[index]); +void +CleanupVectorSlot(VectorTupleTableSlot *vectorSlot) +{ + TupleDesc tupDesc = vectorSlot->tts.tts_tupleDescriptor; - ExecStoreVirtualTuple(out); + int i; + + for (i = 0; i < tupDesc->natts; i++) + { + VectorColumn *column = (VectorColumn *) vectorSlot->tts.tts_values[i]; + memset(column->isnull, true, COLUMNAR_VECTOR_COLUMN_SIZE); + column->dimension = 0; + } + + memset(vectorSlot->keep, true, COLUMNAR_VECTOR_COLUMN_SIZE); + vectorSlot->dimension = 0; } \ No newline at end of file diff --git a/columnar/src/backend/columnar/vectorization/nodes/columnar_aggregator_node.c b/columnar/src/backend/columnar/vectorization/nodes/columnar_aggregator_node.c new file mode 100644 index 00000000..495e47b0 --- /dev/null +++ b/columnar/src/backend/columnar/vectorization/nodes/columnar_aggregator_node.c @@ -0,0 +1,5286 @@ +#include "postgres.h" +#include "pg_version_constants.h" + +#if PG_VERSION_NUM >= PG_VERSION_14 + +/*------------------------------------------------------------------------- + * + * nodeAgg.c + * Routines to handle aggregate nodes. + * + * ExecAgg normally evaluates each aggregate in the following steps: + * + * transvalue = initcond + * foreach input_tuple do + * transvalue = transfunc(transvalue, input_value(s)) + * result = finalfunc(transvalue, direct_argument(s)) + * + * If a finalfunc is not supplied then the result is just the ending + * value of transvalue. + * + * Other behaviors can be selected by the "aggsplit" mode, which exists + * to support partial aggregation. It is possible to: + * * Skip running the finalfunc, so that the output is always the + * final transvalue state. + * * Substitute the combinefunc for the transfunc, so that transvalue + * states (propagated up from a child partial-aggregation step) are merged + * rather than processing raw input rows. (The statements below about + * the transfunc apply equally to the combinefunc, when it's selected.) + * * Apply the serializefunc to the output values (this only makes sense + * when skipping the finalfunc, since the serializefunc works on the + * transvalue data type). + * * Apply the deserializefunc to the input values (this only makes sense + * when using the combinefunc, for similar reasons). + * It is the planner's responsibility to connect up Agg nodes using these + * alternate behaviors in a way that makes sense, with partial aggregation + * results being fed to nodes that expect them. + * + * If a normal aggregate call specifies DISTINCT or ORDER BY, we sort the + * input tuples and eliminate duplicates (if required) before performing + * the above-depicted process. (However, we don't do that for ordered-set + * aggregates; their "ORDER BY" inputs are ordinary aggregate arguments + * so far as this module is concerned.) Note that partial aggregation + * is not supported in these cases, since we couldn't ensure global + * ordering or distinctness of the inputs. + * + * If transfunc is marked "strict" in pg_proc and initcond is NULL, + * then the first non-NULL input_value is assigned directly to transvalue, + * and transfunc isn't applied until the second non-NULL input_value. + * The agg's first input type and transtype must be the same in this case! + * + * If transfunc is marked "strict" then NULL input_values are skipped, + * keeping the previous transvalue. If transfunc is not strict then it + * is called for every input tuple and must deal with NULL initcond + * or NULL input_values for itself. + * + * If finalfunc is marked "strict" then it is not called when the + * ending transvalue is NULL, instead a NULL result is created + * automatically (this is just the usual handling of strict functions, + * of course). A non-strict finalfunc can make its own choice of + * what to return for a NULL ending transvalue. + * + * Ordered-set aggregates are treated specially in one other way: we + * evaluate any "direct" arguments and pass them to the finalfunc along + * with the transition value. + * + * A finalfunc can have additional arguments beyond the transvalue and + * any "direct" arguments, corresponding to the input arguments of the + * aggregate. These are always just passed as NULL. Such arguments may be + * needed to allow resolution of a polymorphic aggregate's result type. + * + * We compute aggregate input expressions and run the transition functions + * in a temporary econtext (aggstate->tmpcontext). This is reset at least + * once per input tuple, so when the transvalue datatype is + * pass-by-reference, we have to be careful to copy it into a longer-lived + * memory context, and free the prior value to avoid memory leakage. We + * store transvalues in another set of econtexts, aggstate->aggcontexts + * (one per grouping set, see below), which are also used for the hashtable + * structures in AGG_HASHED mode. These econtexts are rescanned, not just + * reset, at group boundaries so that aggregate transition functions can + * register shutdown callbacks via AggRegisterCallback. + * + * The node's regular econtext (aggstate->ss.ps.ps_ExprContext) is used to + * run finalize functions and compute the output tuple; this context can be + * reset once per output tuple. + * + * The executor's AggState node is passed as the fmgr "context" value in + * all transfunc and finalfunc calls. It is not recommended that the + * transition functions look at the AggState node directly, but they can + * use AggCheckCallContext() to verify that they are being called by + * nodeAgg.c (and not as ordinary SQL functions). The main reason a + * transition function might want to know this is so that it can avoid + * palloc'ing a fixed-size pass-by-ref transition value on every call: + * it can instead just scribble on and return its left input. Ordinarily + * it is completely forbidden for functions to modify pass-by-ref inputs, + * but in the aggregate case we know the left input is either the initial + * transition value or a previous function result, and in either case its + * value need not be preserved. See int8inc() for an example. Notice that + * the EEOP_AGG_PLAIN_TRANS step is coded to avoid a data copy step when + * the previous transition value pointer is returned. It is also possible + * to avoid repeated data copying when the transition value is an expanded + * object: to do that, the transition function must take care to return + * an expanded object that is in a child context of the memory context + * returned by AggCheckCallContext(). Also, some transition functions want + * to store working state in addition to the nominal transition value; they + * can use the memory context returned by AggCheckCallContext() to do that. + * + * Note: AggCheckCallContext() is available as of PostgreSQL 9.0. The + * AggState is available as context in earlier releases (back to 8.1), + * but direct examination of the node is needed to use it before 9.0. + * + * As of 9.4, aggregate transition functions can also use AggGetAggref() + * to get hold of the Aggref expression node for their aggregate call. + * This is mainly intended for ordered-set aggregates, which are not + * supported as window functions. (A regular aggregate function would + * need some fallback logic to use this, since there's no Aggref node + * for a window function.) + * + * Grouping sets: + * + * A list of grouping sets which is structurally equivalent to a ROLLUP + * clause (e.g. (a,b,c), (a,b), (a)) can be processed in a single pass over + * ordered data. We do this by keeping a separate set of transition values + * for each grouping set being concurrently processed; for each input tuple + * we update them all, and on group boundaries we reset those states + * (starting at the front of the list) whose grouping values have changed + * (the list of grouping sets is ordered from most specific to least + * specific). + * + * Where more complex grouping sets are used, we break them down into + * "phases", where each phase has a different sort order (except phase 0 + * which is reserved for hashing). During each phase but the last, the + * input tuples are additionally stored in a tuplesort which is keyed to the + * next phase's sort order; during each phase but the first, the input + * tuples are drawn from the previously sorted data. (The sorting of the + * data for the first phase is handled by the planner, as it might be + * satisfied by underlying nodes.) + * + * Hashing can be mixed with sorted grouping. To do this, we have an + * AGG_MIXED strategy that populates the hashtables during the first sorted + * phase, and switches to reading them out after completing all sort phases. + * We can also support AGG_HASHED with multiple hash tables and no sorting + * at all. + * + * From the perspective of aggregate transition and final functions, the + * only issue regarding grouping sets is this: a single call site (flinfo) + * of an aggregate function may be used for updating several different + * transition values in turn. So the function must not cache in the flinfo + * anything which logically belongs as part of the transition value (most + * importantly, the memory context in which the transition value exists). + * The support API functions (AggCheckCallContext, AggRegisterCallback) are + * sensitive to the grouping set for which the aggregate function is + * currently being called. + * + * Plan structure: + * + * What we get from the planner is actually one "real" Agg node which is + * part of the plan tree proper, but which optionally has an additional list + * of Agg nodes hung off the side via the "chain" field. This is because an + * Agg node happens to be a convenient representation of all the data we + * need for grouping sets. + * + * For many purposes, we treat the "real" node as if it were just the first + * node in the chain. The chain must be ordered such that hashed entries + * come before sorted/plain entries; the real node is marked AGG_MIXED if + * there are both types present (in which case the real node describes one + * of the hashed groupings, other AGG_HASHED nodes may optionally follow in + * the chain, followed in turn by AGG_SORTED or (one) AGG_PLAIN node). If + * the real node is marked AGG_HASHED or AGG_SORTED, then all the chained + * nodes must be of the same type; if it is AGG_PLAIN, there can be no + * chained nodes. + * + * We collect all hashed nodes into a single "phase", numbered 0, and create + * a sorted phase (numbered 1..n) for each AGG_SORTED or AGG_PLAIN node. + * Phase 0 is allocated even if there are no hashes, but remains unused in + * that case. + * + * AGG_HASHED nodes actually refer to only a single grouping set each, + * because for each hashed grouping we need a separate grpColIdx and + * numGroups estimate. AGG_SORTED nodes represent a "rollup", a list of + * grouping sets that share a sort order. Each AGG_SORTED node other than + * the first one has an associated Sort node which describes the sort order + * to be used; the first sorted node takes its input from the outer subtree, + * which the planner has already arranged to provide ordered data. + * + * Memory and ExprContext usage: + * + * Because we're accumulating aggregate values across input rows, we need to + * use more memory contexts than just simple input/output tuple contexts. + * In fact, for a rollup, we need a separate context for each grouping set + * so that we can reset the inner (finer-grained) aggregates on their group + * boundaries while continuing to accumulate values for outer + * (coarser-grained) groupings. On top of this, we might be simultaneously + * populating hashtables; however, we only need one context for all the + * hashtables. + * + * So we create an array, aggcontexts, with an ExprContext for each grouping + * set in the largest rollup that we're going to process, and use the + * per-tuple memory context of those ExprContexts to store the aggregate + * transition values. hashcontext is the single context created to support + * all hash tables. + * + * Spilling To Disk + * + * When performing hash aggregation, if the hash table memory exceeds the + * limit (see hash_agg_check_limits()), we enter "spill mode". In spill + * mode, we advance the transition states only for groups already in the + * hash table. For tuples that would need to create a new hash table + * entries (and initialize new transition states), we instead spill them to + * disk to be processed later. The tuples are spilled in a partitioned + * manner, so that subsequent batches are smaller and less likely to exceed + * hash_mem (if a batch does exceed hash_mem, it must be spilled + * recursively). + * + * Spilled data is written to logical tapes. These provide better control + * over memory usage, disk space, and the number of files than if we were +#if PG_VERSION_NUM < PG_VERSION_15 + * to use a BufFile for each spill. +#else + * to use a BufFile for each spill. We don't know the number of tapes needed + * at the start of the algorithm (because it can recurse), so a tape set is + * allocated at the beginning, and individual tapes are created as needed. + * As a particular tape is read, logtape.c recycles its disk space. When a + * tape is read to completion, it is destroyed entirely. + * + * Tapes' buffers can take up substantial memory when many tapes are open at + * once. We only need one tape open at a time in read mode (using a buffer + * that's a multiple of BLCKSZ); but we need one tape open in write mode (each + * requiring a buffer of size BLCKSZ) for each partition. +#endif + * + * Note that it's possible for transition states to start small but then + * grow very large; for instance in the case of ARRAY_AGG. In such cases, + * it's still possible to significantly exceed hash_mem. We try to avoid + * this situation by estimating what will fit in the available memory, and + * imposing a limit on the number of groups separately from the amount of + * memory consumed. + * + * Transition / Combine function invocation: + * + * For performance reasons transition functions, including combine + * functions, aren't invoked one-by-one from nodeAgg.c after computing + * arguments using the expression evaluation engine. Instead + * ExecBuildAggTrans() builds one large expression that does both argument + * evaluation and transition function invocation. That avoids performance + * issues due to repeated uses of expression evaluation, complications due + * to filter expressions having to be evaluated early, and allows to JIT + * the entire expression into one native function. + * +#if PG_VERSION_NUM < PG_VERSION_15 + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group +#else + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group +#endif + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/nodeAgg.c + * + *------------------------------------------------------------------------- + */ + +#include "access/htup_details.h" +#include "access/parallel.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "executor/execExpr.h" +#include "executor/executor.h" +#include "executor/nodeAgg.h" +#include "lib/hyperloglog.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "parser/parse_agg.h" +#include "parser/parse_coerce.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/dynahash.h" +#include "utils/expandeddatum.h" +#include "utils/logtape.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/tuplesort.h" + +#include "columnar/vectorization/columnar_vector_types.h" +#include "columnar/vectorization/columnar_vector_execution.h" +#include "columnar/vectorization/nodes/columnar_aggregator_node.h" + +/* + * Control how many partitions are created when spilling HashAgg to + * disk. + * + * HASHAGG_PARTITION_FACTOR is multiplied by the estimated number of + * partitions needed such that each partition will fit in memory. The factor + * is set higher than one because there's not a high cost to having a few too + * many partitions, and it makes it less likely that a partition will need to + * be spilled recursively. Another benefit of having more, smaller partitions + * is that small hash tables may perform better than large ones due to memory + * caching effects. + * + * We also specify a min and max number of partitions per spill. Too few might + * mean a lot of wasted I/O from repeated spilling of the same tuples. Too + * many will result in lots of memory wasted buffering the spill files (which + * could instead be spent on a larger hash table). + */ +#define HASHAGG_PARTITION_FACTOR 1.50 +#define HASHAGG_MIN_PARTITIONS 4 +#define HASHAGG_MAX_PARTITIONS 1024 + +/* + * For reading from tapes, the buffer size must be a multiple of + * BLCKSZ. Larger values help when reading from multiple tapes concurrently, + * but that doesn't happen in HashAgg, so we simply use BLCKSZ. Writing to a + * tape always uses a buffer of size BLCKSZ. + */ +#define HASHAGG_READ_BUFFER_SIZE BLCKSZ +#define HASHAGG_WRITE_BUFFER_SIZE BLCKSZ + +/* + * HyperLogLog is used for estimating the cardinality of the spilled tuples in + * a given partition. 5 bits corresponds to a size of about 32 bytes and a + * worst-case error of around 18%. That's effective enough to choose a + * reasonable number of partitions when recursing. + */ +#define HASHAGG_HLL_BIT_WIDTH 5 + +/* + * Estimate chunk overhead as a constant 16 bytes. XXX: should this be + * improved? + */ +#define CHUNKHDRSZ 16 + +#if PG_VERSION_NUM < PG_VERSION_15 +/* + * Track all tapes needed for a HashAgg that spills. We don't know the maximum + * number of tapes needed at the start of the algorithm (because it can + * recurse), so one tape set is allocated and extended as needed for new + * tapes. When a particular tape is already read, rewind it for write mode and + * put it in the free list. + * + * Tapes' buffers can take up substantial memory when many tapes are open at + * once. We only need one tape open at a time in read mode (using a buffer + * that's a multiple of BLCKSZ); but we need one tape open in write mode (each + * requiring a buffer of size BLCKSZ) for each partition. + */ +typedef struct HashTapeInfo +{ + LogicalTapeSet *tapeset; + int ntapes; + int *freetapes; + int nfreetapes; + int freetapes_alloc; +} HashTapeInfo; +#endif + +/* + * Represents partitioned spill data for a single hashtable. Contains the + * necessary information to route tuples to the correct partition, and to + * transform the spilled data into new batches. + * + * The high bits are used for partition selection (when recursing, we ignore + * the bits that have already been used for partition selection at an earlier + * level). + */ +typedef struct HashAggSpill +{ +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeSet *tapeset; /* borrowed reference to tape set */ +#endif + int npartitions; /* number of partitions */ +#if PG_VERSION_NUM < PG_VERSION_15 + int *partitions; /* spill partition tape numbers */ +#else + LogicalTape **partitions; /* spill partition tapes */ +#endif + int64 *ntuples; /* number of tuples in each partition */ + uint32 mask; /* mask to find partition from hash value */ + int shift; /* after masking, shift by this amount */ + hyperLogLogState *hll_card; /* cardinality estimate for contents */ +} HashAggSpill; + +/* + * Represents work to be done for one pass of hash aggregation (with only one + * grouping set). + * + * Also tracks the bits of the hash already used for partition selection by + * earlier iterations, so that this batch can use new bits. If all bits have + * already been used, no partitioning will be done (any spilled data will go + * to a single output tape). + */ +typedef struct HashAggBatch +{ + int setno; /* grouping set */ + int used_bits; /* number of bits of hash already used */ +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeSet *tapeset; /* borrowed reference to tape set */ + int input_tapenum; /* input partition tape */ +#else + LogicalTape *input_tape; /* input partition tape */ +#endif + int64 input_tuples; /* number of tuples in this batch */ + double input_card; /* estimated group cardinality */ +} HashAggBatch; + +/* used to find referenced colnos */ +typedef struct FindColsContext +{ + bool is_aggref; /* is under an aggref */ + Bitmapset *aggregated; /* column references under an aggref */ + Bitmapset *unaggregated; /* other column references */ +} FindColsContext; + +static void select_current_set(AggState *aggstate, int setno, bool is_hash); +static void initialize_phase(AggState *aggstate, int newphase); +static TupleTableSlot *fetch_input_tuple(AggState *aggstate); +static void initialize_aggregates(AggState *aggstate, + AggStatePerGroup *pergroups, + int numReset); +static void advance_transition_function(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate); +static void advance_aggregates(AggState *aggstate); +static void process_ordered_aggregate_single(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate); +static void process_ordered_aggregate_multi(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate); +static void finalize_aggregate(AggState *aggstate, + AggStatePerAgg peragg, + AggStatePerGroup pergroupstate, + Datum *resultVal, bool *resultIsNull); +static void finalize_partialaggregate(AggState *aggstate, + AggStatePerAgg peragg, + AggStatePerGroup pergroupstate, + Datum *resultVal, bool *resultIsNull); +static inline void prepare_hash_slot(AggStatePerHash perhash, + TupleTableSlot *inputslot, + TupleTableSlot *hashslot); +static void prepare_projection_slot(AggState *aggstate, + TupleTableSlot *slot, + int currentSet); +static void finalize_aggregates(AggState *aggstate, + AggStatePerAgg peragg, + AggStatePerGroup pergroup); +static TupleTableSlot *project_aggregates(AggState *aggstate); +static void find_cols(AggState *aggstate, Bitmapset **aggregated, + Bitmapset **unaggregated); +static bool find_cols_walker(Node *node, FindColsContext *context); +static void build_hash_tables(AggState *aggstate); +static void build_hash_table(AggState *aggstate, int setno, long nbuckets); +static void hashagg_recompile_expressions(AggState *aggstate, bool minslot, + bool nullcheck); +static long hash_choose_num_buckets(double hashentrysize, + long estimated_nbuckets, + Size memory); +static int hash_choose_num_partitions(double input_groups, + double hashentrysize, + int used_bits, + int *log2_npartittions); +static void initialize_hash_entry(AggState *aggstate, + TupleHashTable hashtable, + TupleHashEntry entry); +static void lookup_hash_entries(AggState *aggstate); +static TupleTableSlot *agg_retrieve_direct(VectorAggState *vectoraggstate); +static void agg_fill_hash_table(AggState *aggstate); +static bool agg_refill_hash_table(AggState *aggstate); +static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate); +static TupleTableSlot *agg_retrieve_hash_table_in_memory(AggState *aggstate); +static void hash_agg_check_limits(AggState *aggstate); +static void hash_agg_enter_spill_mode(AggState *aggstate); +static void hash_agg_update_metrics(AggState *aggstate, bool from_tape, + int npartitions); +static void hashagg_finish_initial_spills(AggState *aggstate); +static void hashagg_reset_spill_state(AggState *aggstate); +#if PG_VERSION_NUM < PG_VERSION_15 +static HashAggBatch *hashagg_batch_new(LogicalTapeSet *tapeset, + int input_tapenum, int setno, +#else +static HashAggBatch *hashagg_batch_new(LogicalTape *input_tape, int setno, +#endif + int64 input_tuples, double input_card, + int used_bits); +static MinimalTuple hashagg_batch_read(HashAggBatch *batch, uint32 *hashp); +#if PG_VERSION_NUM < PG_VERSION_15 +static void hashagg_spill_init(HashAggSpill *spill, HashTapeInfo *tapeinfo, +#else +static void hashagg_spill_init(HashAggSpill *spill, LogicalTapeSet *lts, +#endif + int used_bits, double input_groups, + double hashentrysize); +static Size hashagg_spill_tuple(AggState *aggstate, HashAggSpill *spill, + TupleTableSlot *slot, uint32 hash); +static void hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, + int setno); +#if PG_VERSION_NUM < PG_VERSION_15 +static void hashagg_tapeinfo_init(AggState *aggstate); +static void hashagg_tapeinfo_assign(HashTapeInfo *tapeinfo, int *dest, + int ndest); +static void hashagg_tapeinfo_release(HashTapeInfo *tapeinfo, int tapenum); +#endif +static Datum GetAggInitVal(Datum textInitVal, Oid transtype); +static void build_pertrans_for_aggref(AggStatePerTrans pertrans, + AggState *aggstate, EState *estate, +#if PG_VERSION_NUM < PG_VERSION_15 + Aggref *aggref, Oid aggtransfn, Oid aggtranstype, + Oid aggserialfn, Oid aggdeserialfn, + Datum initValue, bool initValueIsNull, + Oid *inputTypes, int numArguments); +#else + Aggref *aggref, Oid transfn_oid, + Oid aggtranstype, Oid aggserialfn, + Oid aggdeserialfn, Datum initValue, + bool initValueIsNull, Oid *inputTypes, + int numArguments); +#endif +static AggState * VExecInitAgg(Agg *node, EState *estate, int eflags); + +/* + * Select the current grouping set; affects current_set and + * curaggcontext. + */ +static void +select_current_set(AggState *aggstate, int setno, bool is_hash) +{ + /* + * When changing this, also adapt ExecAggPlainTransByVal() and + * ExecAggPlainTransByRef(). + */ + if (is_hash) + aggstate->curaggcontext = aggstate->hashcontext; + else + aggstate->curaggcontext = aggstate->aggcontexts[setno]; + + aggstate->current_set = setno; +} + +/* + * Switch to phase "newphase", which must either be 0 or 1 (to reset) or + * current_phase + 1. Juggle the tuplesorts accordingly. + * + * Phase 0 is for hashing, which we currently handle last in the AGG_MIXED + * case, so when entering phase 0, all we need to do is drop open sorts. + */ +static void +initialize_phase(AggState *aggstate, int newphase) +{ + Assert(newphase <= 1 || newphase == aggstate->current_phase + 1); + + /* + * Whatever the previous state, we're now done with whatever input + * tuplesort was in use. + */ + if (aggstate->sort_in) + { + tuplesort_end(aggstate->sort_in); + aggstate->sort_in = NULL; + } + + if (newphase <= 1) + { + /* + * Discard any existing output tuplesort. + */ + if (aggstate->sort_out) + { + tuplesort_end(aggstate->sort_out); + aggstate->sort_out = NULL; + } + } + else + { + /* + * The old output tuplesort becomes the new input one, and this is the + * right time to actually sort it. + */ + aggstate->sort_in = aggstate->sort_out; + aggstate->sort_out = NULL; + Assert(aggstate->sort_in); + tuplesort_performsort(aggstate->sort_in); + } + + /* + * If this isn't the last phase, we need to sort appropriately for the + * next phase in sequence. + */ + if (newphase > 0 && newphase < aggstate->numphases - 1) + { + Sort *sortnode = aggstate->phases[newphase + 1].sortnode; + PlanState *outerNode = outerPlanState(aggstate); + TupleDesc tupDesc = ExecGetResultType(outerNode); + + aggstate->sort_out = tuplesort_begin_heap(tupDesc, + sortnode->numCols, + sortnode->sortColIdx, + sortnode->sortOperators, + sortnode->collations, + sortnode->nullsFirst, + work_mem, +#if PG_VERSION_NUM < PG_VERSION_15 + NULL, false); +#else + NULL, TUPLESORT_NONE); +#endif + } + + aggstate->current_phase = newphase; + aggstate->phase = &aggstate->phases[newphase]; +} + +/* + * Fetch a tuple from either the outer plan (for phase 1) or from the sorter + * populated by the previous phase. Copy it to the sorter for the next phase + * if any. + * + * Callers cannot rely on memory for tuple in returned slot remaining valid + * past any subsequently fetched tuple. + */ +static TupleTableSlot * +fetch_input_tuple(AggState *aggstate) +{ + TupleTableSlot *slot; + + if (aggstate->sort_in) + { + /* make sure we check for interrupts in either path through here */ + CHECK_FOR_INTERRUPTS(); + if (!tuplesort_gettupleslot(aggstate->sort_in, true, false, + aggstate->sort_slot, NULL)) + return NULL; + slot = aggstate->sort_slot; + } + else + slot = ExecProcNode(outerPlanState(aggstate)); + + if (!TupIsNull(slot) && aggstate->sort_out) + tuplesort_puttupleslot(aggstate->sort_out, slot); + + return slot; +} + +/* + * (Re)Initialize an individual aggregate. + * + * This function handles only one grouping set, already set in + * aggstate->current_set. + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +initialize_aggregate(AggState *aggstate, AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate) +{ + /* + * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate. + */ + if (pertrans->numSortCols > 0) + { + /* + * In case of rescan, maybe there could be an uncompleted sort + * operation? Clean it up if so. + */ + if (pertrans->sortstates[aggstate->current_set]) + tuplesort_end(pertrans->sortstates[aggstate->current_set]); + + + /* + * We use a plain Datum sorter when there's a single input column; + * otherwise sort the full tuple. (See comments for + * process_ordered_aggregate_single.) + */ + if (pertrans->numInputs == 1) + { + Form_pg_attribute attr = TupleDescAttr(pertrans->sortdesc, 0); + + pertrans->sortstates[aggstate->current_set] = + tuplesort_begin_datum(attr->atttypid, + pertrans->sortOperators[0], + pertrans->sortCollations[0], + pertrans->sortNullsFirst[0], +#if PG_VERSION_NUM < PG_VERSION_15 + work_mem, NULL, false); +#else + work_mem, NULL, TUPLESORT_NONE); +#endif + } + else + pertrans->sortstates[aggstate->current_set] = + tuplesort_begin_heap(pertrans->sortdesc, + pertrans->numSortCols, + pertrans->sortColIdx, + pertrans->sortOperators, + pertrans->sortCollations, + pertrans->sortNullsFirst, +#if PG_VERSION_NUM < PG_VERSION_15 + work_mem, NULL, false); +#else + work_mem, NULL, TUPLESORT_NONE); +#endif + } + + /* + * (Re)set transValue to the initial value. + * + * Note that when the initial value is pass-by-ref, we must copy it (into + * the aggcontext) since we will pfree the transValue later. + */ + if (pertrans->initValueIsNull) + pergroupstate->transValue = pertrans->initValue; + else + { + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory); + pergroupstate->transValue = datumCopy(pertrans->initValue, + pertrans->transtypeByVal, + pertrans->transtypeLen); + MemoryContextSwitchTo(oldContext); + } + pergroupstate->transValueIsNull = pertrans->initValueIsNull; + + /* + * If the initial value for the transition state doesn't exist in the + * pg_aggregate table then we will let the first non-NULL value returned + * from the outer procNode become the initial value. (This is useful for + * aggregates like max() and min().) The noTransValue flag signals that we + * still need to do this. + */ + pergroupstate->noTransValue = pertrans->initValueIsNull; +} + +/* + * Initialize all aggregate transition states for a new group of input values. + * + * If there are multiple grouping sets, we initialize only the first numReset + * of them (the grouping sets are ordered so that the most specific one, which + * is reset most often, is first). As a convenience, if numReset is 0, we + * reinitialize all sets. + * + * NB: This cannot be used for hash aggregates, as for those the grouping set + * number has to be specified from further up. + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +initialize_aggregates(AggState *aggstate, + AggStatePerGroup *pergroups, + int numReset) +{ + int transno; + int numGroupingSets = Max(aggstate->phase->numsets, 1); + int setno = 0; + int numTrans = aggstate->numtrans; + AggStatePerTrans transstates = aggstate->pertrans; + + if (numReset == 0) + numReset = numGroupingSets; + + for (setno = 0; setno < numReset; setno++) + { + AggStatePerGroup pergroup = pergroups[setno]; + + select_current_set(aggstate, setno, false); + + for (transno = 0; transno < numTrans; transno++) + { + AggStatePerTrans pertrans = &transstates[transno]; + AggStatePerGroup pergroupstate = &pergroup[transno]; + + initialize_aggregate(aggstate, pertrans, pergroupstate); + } + } +} + +/* + * Given new input value(s), advance the transition function of one aggregate + * state within one grouping set only (already set in aggstate->current_set) + * + * The new values (and null flags) have been preloaded into argument positions + * 1 and up in pertrans->transfn_fcinfo, so that we needn't copy them again to + * pass to the transition function. We also expect that the static fields of + * the fcinfo are already initialized; that was done by ExecInitAgg(). + * + * It doesn't matter which memory context this is called in. + */ +static void +advance_transition_function(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate) +{ + FunctionCallInfo fcinfo = pertrans->transfn_fcinfo; + MemoryContext oldContext; + Datum newVal; + + if (pertrans->transfn.fn_strict) + { + /* + * For a strict transfn, nothing happens when there's a NULL input; we + * just keep the prior transValue. + */ + int numTransInputs = pertrans->numTransInputs; + int i; + + for (i = 1; i <= numTransInputs; i++) + { + if (fcinfo->args[i].isnull) + return; + } + if (pergroupstate->noTransValue) + { + /* + * transValue has not been initialized. This is the first non-NULL + * input value. We use it as the initial value for transValue. (We + * already checked that the agg's input type is binary-compatible + * with its transtype, so straight copy here is OK.) + * + * We must copy the datum into aggcontext if it is pass-by-ref. We + * do not need to pfree the old transValue, since it's NULL. + */ + oldContext = MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory); + pergroupstate->transValue = datumCopy(fcinfo->args[1].value, + pertrans->transtypeByVal, + pertrans->transtypeLen); + pergroupstate->transValueIsNull = false; + pergroupstate->noTransValue = false; + MemoryContextSwitchTo(oldContext); + return; + } + if (pergroupstate->transValueIsNull) + { + /* + * Don't call a strict function with NULL inputs. Note it is + * possible to get here despite the above tests, if the transfn is + * strict *and* returned a NULL on a prior cycle. If that happens + * we will propagate the NULL all the way to the end. + */ + return; + } + } + + /* We run the transition functions in per-input-tuple memory context */ + oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory); + + /* set up aggstate->curpertrans for AggGetAggref() */ + aggstate->curpertrans = pertrans; + + /* + * OK to call the transition function + */ + fcinfo->args[0].value = pergroupstate->transValue; + fcinfo->args[0].isnull = pergroupstate->transValueIsNull; + fcinfo->isnull = false; /* just in case transfn doesn't set it */ + + newVal = FunctionCallInvoke(fcinfo); + + aggstate->curpertrans = NULL; + + /* + * If pass-by-ref datatype, must copy the new value into aggcontext and + * free the prior transValue. But if transfn returned a pointer to its + * first input, we don't need to do anything. Also, if transfn returned a + * pointer to a R/W expanded object that is already a child of the + * aggcontext, assume we can adopt that value without copying it. + * + * It's safe to compare newVal with pergroup->transValue without regard + * for either being NULL, because ExecAggTransReparent() takes care to set + * transValue to 0 when NULL. Otherwise we could end up accidentally not + * reparenting, when the transValue has the same numerical value as + * newValue, despite being NULL. This is a somewhat hot path, making it + * undesirable to instead solve this with another branch for the common + * case of the transition function returning its (modified) input + * argument. + */ + if (!pertrans->transtypeByVal && + DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue)) + newVal = ExecAggTransReparent(aggstate, pertrans, + newVal, fcinfo->isnull, + pergroupstate->transValue, + pergroupstate->transValueIsNull); + + pergroupstate->transValue = newVal; + pergroupstate->transValueIsNull = fcinfo->isnull; + + MemoryContextSwitchTo(oldContext); +} + +/* + * Advance each aggregate transition state for one input tuple. The input + * tuple has been stored in tmpcontext->ecxt_outertuple, so that it is + * accessible to ExecEvalExpr. + * + * We have two sets of transition states to handle: one for sorted aggregation + * and one for hashed; we do them both here, to avoid multiple evaluation of + * the inputs. + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +advance_aggregates(AggState *aggstate) +{ + bool dummynull; + + ExecEvalExprSwitchContext(aggstate->phase->evaltrans, + aggstate->tmpcontext, + &dummynull); +} + +/* + * Run the transition function for a DISTINCT or ORDER BY aggregate + * with only one input. This is called after we have completed + * entering all the input values into the sort object. We complete the + * sort, read out the values in sorted order, and run the transition + * function on each value (applying DISTINCT if appropriate). + * + * Note that the strictness of the transition function was checked when + * entering the values into the sort, so we don't check it again here; + * we just apply standard SQL DISTINCT logic. + * + * The one-input case is handled separately from the multi-input case + * for performance reasons: for single by-value inputs, such as the + * common case of count(distinct id), the tuplesort_getdatum code path + * is around 300% faster. (The speedup for by-reference types is less + * but still noticeable.) + * + * This function handles only one grouping set (already set in + * aggstate->current_set). + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +process_ordered_aggregate_single(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate) +{ + Datum oldVal = (Datum) 0; + bool oldIsNull = true; + bool haveOldVal = false; + MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory; + MemoryContext oldContext; + bool isDistinct = (pertrans->numDistinctCols > 0); + Datum newAbbrevVal = (Datum) 0; + Datum oldAbbrevVal = (Datum) 0; + FunctionCallInfo fcinfo = pertrans->transfn_fcinfo; + Datum *newVal; + bool *isNull; + + Assert(pertrans->numDistinctCols < 2); + + tuplesort_performsort(pertrans->sortstates[aggstate->current_set]); + + /* Load the column into argument 1 (arg 0 will be transition value) */ + newVal = &fcinfo->args[1].value; + isNull = &fcinfo->args[1].isnull; + + /* + * Note: if input type is pass-by-ref, the datums returned by the sort are + * freshly palloc'd in the per-query context, so we must be careful to + * pfree them when they are no longer needed. + */ + + while (tuplesort_getdatum(pertrans->sortstates[aggstate->current_set], + true, newVal, isNull, &newAbbrevVal)) + { + /* + * Clear and select the working context for evaluation of the equality + * function and transition function. + */ + MemoryContextReset(workcontext); + oldContext = MemoryContextSwitchTo(workcontext); + + /* + * If DISTINCT mode, and not distinct from prior, skip it. + */ + if (isDistinct && + haveOldVal && + ((oldIsNull && *isNull) || + (!oldIsNull && !*isNull && + oldAbbrevVal == newAbbrevVal && + DatumGetBool(FunctionCall2Coll(&pertrans->equalfnOne, + pertrans->aggCollation, + oldVal, *newVal))))) + { + /* equal to prior, so forget this one */ + if (!pertrans->inputtypeByVal && !*isNull) + pfree(DatumGetPointer(*newVal)); + } + else + { + advance_transition_function(aggstate, pertrans, pergroupstate); + /* forget the old value, if any */ + if (!oldIsNull && !pertrans->inputtypeByVal) + pfree(DatumGetPointer(oldVal)); + /* and remember the new one for subsequent equality checks */ + oldVal = *newVal; + oldAbbrevVal = newAbbrevVal; + oldIsNull = *isNull; + haveOldVal = true; + } + + MemoryContextSwitchTo(oldContext); + } + + if (!oldIsNull && !pertrans->inputtypeByVal) + pfree(DatumGetPointer(oldVal)); + + tuplesort_end(pertrans->sortstates[aggstate->current_set]); + pertrans->sortstates[aggstate->current_set] = NULL; +} + +/* + * Run the transition function for a DISTINCT or ORDER BY aggregate + * with more than one input. This is called after we have completed + * entering all the input values into the sort object. We complete the + * sort, read out the values in sorted order, and run the transition + * function on each value (applying DISTINCT if appropriate). + * + * This function handles only one grouping set (already set in + * aggstate->current_set). + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +process_ordered_aggregate_multi(AggState *aggstate, + AggStatePerTrans pertrans, + AggStatePerGroup pergroupstate) +{ + ExprContext *tmpcontext = aggstate->tmpcontext; + FunctionCallInfo fcinfo = pertrans->transfn_fcinfo; + TupleTableSlot *slot1 = pertrans->sortslot; + TupleTableSlot *slot2 = pertrans->uniqslot; + int numTransInputs = pertrans->numTransInputs; + int numDistinctCols = pertrans->numDistinctCols; + Datum newAbbrevVal = (Datum) 0; + Datum oldAbbrevVal = (Datum) 0; + bool haveOldValue = false; + TupleTableSlot *save = aggstate->tmpcontext->ecxt_outertuple; + int i; + + tuplesort_performsort(pertrans->sortstates[aggstate->current_set]); + + ExecClearTuple(slot1); + if (slot2) + ExecClearTuple(slot2); + + while (tuplesort_gettupleslot(pertrans->sortstates[aggstate->current_set], + true, true, slot1, &newAbbrevVal)) + { + CHECK_FOR_INTERRUPTS(); + + tmpcontext->ecxt_outertuple = slot1; + tmpcontext->ecxt_innertuple = slot2; + + if (numDistinctCols == 0 || + !haveOldValue || + newAbbrevVal != oldAbbrevVal || + !ExecQual(pertrans->equalfnMulti, tmpcontext)) + { + /* + * Extract the first numTransInputs columns as datums to pass to + * the transfn. + */ + slot_getsomeattrs(slot1, numTransInputs); + + /* Load values into fcinfo */ + /* Start from 1, since the 0th arg will be the transition value */ + for (i = 0; i < numTransInputs; i++) + { + fcinfo->args[i + 1].value = slot1->tts_values[i]; + fcinfo->args[i + 1].isnull = slot1->tts_isnull[i]; + } + + advance_transition_function(aggstate, pertrans, pergroupstate); + + if (numDistinctCols > 0) + { + /* swap the slot pointers to retain the current tuple */ + TupleTableSlot *tmpslot = slot2; + + slot2 = slot1; + slot1 = tmpslot; + /* avoid ExecQual() calls by reusing abbreviated keys */ + oldAbbrevVal = newAbbrevVal; + haveOldValue = true; + } + } + + /* Reset context each time */ + ResetExprContext(tmpcontext); + + ExecClearTuple(slot1); + } + + if (slot2) + ExecClearTuple(slot2); + + tuplesort_end(pertrans->sortstates[aggstate->current_set]); + pertrans->sortstates[aggstate->current_set] = NULL; + + /* restore previous slot, potentially in use for grouping sets */ + tmpcontext->ecxt_outertuple = save; +} + +/* + * Compute the final value of one aggregate. + * + * This function handles only one grouping set (already set in + * aggstate->current_set). + * + * The finalfn will be run, and the result delivered, in the + * output-tuple context; caller's CurrentMemoryContext does not matter. + * + * The finalfn uses the state as set in the transno. This also might be + * being used by another aggregate function, so it's important that we do + * nothing destructive here. + */ +static void +finalize_aggregate(AggState *aggstate, + AggStatePerAgg peragg, + AggStatePerGroup pergroupstate, + Datum *resultVal, bool *resultIsNull) +{ + LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS); + bool anynull = false; + MemoryContext oldContext; + int i; + ListCell *lc; + AggStatePerTrans pertrans = &aggstate->pertrans[peragg->transno]; + + oldContext = MemoryContextSwitchTo(aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory); + + /* + * Evaluate any direct arguments. We do this even if there's no finalfn + * (which is unlikely anyway), so that side-effects happen as expected. + * The direct arguments go into arg positions 1 and up, leaving position 0 + * for the transition state value. + */ + i = 1; + foreach(lc, peragg->aggdirectargs) + { + ExprState *expr = (ExprState *) lfirst(lc); + + fcinfo->args[i].value = ExecEvalExpr(expr, + aggstate->ss.ps.ps_ExprContext, + &fcinfo->args[i].isnull); + anynull |= fcinfo->args[i].isnull; + i++; + } + + /* + * Apply the agg's finalfn if one is provided, else return transValue. + */ + if (OidIsValid(peragg->finalfn_oid)) + { + int numFinalArgs = peragg->numFinalArgs; + + /* set up aggstate->curperagg for AggGetAggref() */ + aggstate->curperagg = peragg; + + InitFunctionCallInfoData(*fcinfo, &peragg->finalfn, + numFinalArgs, + pertrans->aggCollation, + (void *) aggstate, NULL); + + /* Fill in the transition state value */ + fcinfo->args[0].value = + MakeExpandedObjectReadOnly(pergroupstate->transValue, + pergroupstate->transValueIsNull, + pertrans->transtypeLen); + fcinfo->args[0].isnull = pergroupstate->transValueIsNull; + anynull |= pergroupstate->transValueIsNull; + + /* Fill any remaining argument positions with nulls */ + for (; i < numFinalArgs; i++) + { + fcinfo->args[i].value = (Datum) 0; + fcinfo->args[i].isnull = true; + anynull = true; + } + + if (fcinfo->flinfo->fn_strict && anynull) + { + /* don't call a strict function with NULL inputs */ + *resultVal = (Datum) 0; + *resultIsNull = true; + } + else + { + *resultVal = FunctionCallInvoke(fcinfo); + *resultIsNull = fcinfo->isnull; + } + aggstate->curperagg = NULL; + } + else + { + /* Don't need MakeExpandedObjectReadOnly; datumCopy will copy it */ + *resultVal = pergroupstate->transValue; + *resultIsNull = pergroupstate->transValueIsNull; + } + + /* + * If result is pass-by-ref, make sure it is in the right context. + */ + if (!peragg->resulttypeByVal && !*resultIsNull && + !MemoryContextContains(CurrentMemoryContext, + DatumGetPointer(*resultVal))) + *resultVal = datumCopy(*resultVal, + peragg->resulttypeByVal, + peragg->resulttypeLen); + + MemoryContextSwitchTo(oldContext); +} + +/* + * Compute the output value of one partial aggregate. + * + * The serialization function will be run, and the result delivered, in the + * output-tuple context; caller's CurrentMemoryContext does not matter. + */ +static void +finalize_partialaggregate(AggState *aggstate, + AggStatePerAgg peragg, + AggStatePerGroup pergroupstate, + Datum *resultVal, bool *resultIsNull) +{ + AggStatePerTrans pertrans = &aggstate->pertrans[peragg->transno]; + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory); + + /* + * serialfn_oid will be set if we must serialize the transvalue before + * returning it + */ + if (OidIsValid(pertrans->serialfn_oid)) + { + /* Don't call a strict serialization function with NULL input. */ + if (pertrans->serialfn.fn_strict && pergroupstate->transValueIsNull) + { + *resultVal = (Datum) 0; + *resultIsNull = true; + } + else + { + FunctionCallInfo fcinfo = pertrans->serialfn_fcinfo; + + fcinfo->args[0].value = + MakeExpandedObjectReadOnly(pergroupstate->transValue, + pergroupstate->transValueIsNull, + pertrans->transtypeLen); + fcinfo->args[0].isnull = pergroupstate->transValueIsNull; + fcinfo->isnull = false; + + *resultVal = FunctionCallInvoke(fcinfo); + *resultIsNull = fcinfo->isnull; + } + } + else + { + /* Don't need MakeExpandedObjectReadOnly; datumCopy will copy it */ + *resultVal = pergroupstate->transValue; + *resultIsNull = pergroupstate->transValueIsNull; + } + + /* If result is pass-by-ref, make sure it is in the right context. */ + if (!peragg->resulttypeByVal && !*resultIsNull && + !MemoryContextContains(CurrentMemoryContext, + DatumGetPointer(*resultVal))) + *resultVal = datumCopy(*resultVal, + peragg->resulttypeByVal, + peragg->resulttypeLen); + + MemoryContextSwitchTo(oldContext); +} + +/* + * Extract the attributes that make up the grouping key into the + * hashslot. This is necessary to compute the hash or perform a lookup. + */ +static inline void +prepare_hash_slot(AggStatePerHash perhash, + TupleTableSlot *inputslot, + TupleTableSlot *hashslot) +{ + int i; + + /* transfer just the needed columns into hashslot */ + slot_getsomeattrs(inputslot, perhash->largestGrpColIdx); + ExecClearTuple(hashslot); + + for (i = 0; i < perhash->numhashGrpCols; i++) + { + int varNumber = perhash->hashGrpColIdxInput[i] - 1; + + hashslot->tts_values[i] = inputslot->tts_values[varNumber]; + hashslot->tts_isnull[i] = inputslot->tts_isnull[varNumber]; + } + ExecStoreVirtualTuple(hashslot); +} + +/* + * Prepare to finalize and project based on the specified representative tuple + * slot and grouping set. + * + * In the specified tuple slot, force to null all attributes that should be + * read as null in the context of the current grouping set. Also stash the + * current group bitmap where GroupingExpr can get at it. + * + * This relies on three conditions: + * + * 1) Nothing is ever going to try and extract the whole tuple from this slot, + * only reference it in evaluations, which will only access individual + * attributes. + * + * 2) No system columns are going to need to be nulled. (If a system column is + * referenced in a group clause, it is actually projected in the outer plan + * tlist.) + * + * 3) Within a given phase, we never need to recover the value of an attribute + * once it has been set to null. + * + * Poking into the slot this way is a bit ugly, but the consensus is that the + * alternative was worse. + */ +static void +prepare_projection_slot(AggState *aggstate, TupleTableSlot *slot, int currentSet) +{ + if (aggstate->phase->grouped_cols) + { + Bitmapset *grouped_cols = aggstate->phase->grouped_cols[currentSet]; + + aggstate->grouped_cols = grouped_cols; + + if (TTS_EMPTY(slot)) + { + /* + * Force all values to be NULL if working on an empty input tuple + * (i.e. an empty grouping set for which no input rows were + * supplied). + */ + ExecStoreAllNullTuple(slot); + } + else if (aggstate->all_grouped_cols) + { + ListCell *lc; + + /* all_grouped_cols is arranged in desc order */ + slot_getsomeattrs(slot, linitial_int(aggstate->all_grouped_cols)); + + foreach(lc, aggstate->all_grouped_cols) + { + int attnum = lfirst_int(lc); + + if (!bms_is_member(attnum, grouped_cols)) + slot->tts_isnull[attnum - 1] = true; + } + } + } +} + +/* + * Compute the final value of all aggregates for one group. + * + * This function handles only one grouping set at a time, which the caller must + * have selected. It's also the caller's responsibility to adjust the supplied + * pergroup parameter to point to the current set's transvalues. + * + * Results are stored in the output econtext aggvalues/aggnulls. + */ +static void +finalize_aggregates(AggState *aggstate, + AggStatePerAgg peraggs, + AggStatePerGroup pergroup) +{ + ExprContext *econtext = aggstate->ss.ps.ps_ExprContext; + Datum *aggvalues = econtext->ecxt_aggvalues; + bool *aggnulls = econtext->ecxt_aggnulls; + int aggno; + int transno; + + /* + * If there were any DISTINCT and/or ORDER BY aggregates, sort their + * inputs and run the transition functions. + */ + for (transno = 0; transno < aggstate->numtrans; transno++) + { + AggStatePerTrans pertrans = &aggstate->pertrans[transno]; + AggStatePerGroup pergroupstate; + + pergroupstate = &pergroup[transno]; + + if (pertrans->numSortCols > 0) + { + Assert(aggstate->aggstrategy != AGG_HASHED && + aggstate->aggstrategy != AGG_MIXED); + + if (pertrans->numInputs == 1) + process_ordered_aggregate_single(aggstate, + pertrans, + pergroupstate); + else + process_ordered_aggregate_multi(aggstate, + pertrans, + pergroupstate); + } + } + + /* + * Run the final functions. + */ + for (aggno = 0; aggno < aggstate->numaggs; aggno++) + { + AggStatePerAgg peragg = &peraggs[aggno]; + transno = peragg->transno; + AggStatePerGroup pergroupstate; + + pergroupstate = &pergroup[transno]; + + if (DO_AGGSPLIT_SKIPFINAL(aggstate->aggsplit)) + finalize_partialaggregate(aggstate, peragg, pergroupstate, + &aggvalues[aggno], &aggnulls[aggno]); + else + finalize_aggregate(aggstate, peragg, pergroupstate, + &aggvalues[aggno], &aggnulls[aggno]); + } +} + +/* + * Project the result of a group (whose aggs have already been calculated by + * finalize_aggregates). Returns the result slot, or NULL if no row is + * projected (suppressed by qual). + */ +static TupleTableSlot * +project_aggregates(AggState *aggstate) +{ + ExprContext *econtext = aggstate->ss.ps.ps_ExprContext; + + /* + * Check the qual (HAVING clause); if the group does not match, ignore it. + */ + if (ExecQual(aggstate->ss.ps.qual, econtext)) + { + /* + * Form and return projection tuple using the aggregate results and + * the representative input tuple. + */ + return ExecProject(aggstate->ss.ps.ps_ProjInfo); + } + else + InstrCountFiltered1(aggstate, 1); + + return NULL; +} + +/* + * Find input-tuple columns that are needed, dividing them into + * aggregated and unaggregated sets. + */ +static void +find_cols(AggState *aggstate, Bitmapset **aggregated, Bitmapset **unaggregated) +{ + Agg *agg = (Agg *) aggstate->ss.ps.plan; + FindColsContext context; + + context.is_aggref = false; + context.aggregated = NULL; + context.unaggregated = NULL; + + /* Examine tlist and quals */ + (void) find_cols_walker((Node *) agg->plan.targetlist, &context); + (void) find_cols_walker((Node *) agg->plan.qual, &context); + + /* In some cases, grouping columns will not appear in the tlist */ + for (int i = 0; i < agg->numCols; i++) + context.unaggregated = bms_add_member(context.unaggregated, + agg->grpColIdx[i]); + + *aggregated = context.aggregated; + *unaggregated = context.unaggregated; +} + +static bool +find_cols_walker(Node *node, FindColsContext *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + /* setrefs.c should have set the varno to OUTER_VAR */ + Assert(var->varno == OUTER_VAR); + Assert(var->varlevelsup == 0); + if (context->is_aggref) + context->aggregated = bms_add_member(context->aggregated, + var->varattno); + else + context->unaggregated = bms_add_member(context->unaggregated, + var->varattno); + return false; + } + if (IsA(node, Aggref)) + { + Assert(!context->is_aggref); + context->is_aggref = true; + expression_tree_walker(node, find_cols_walker, (void *) context); + context->is_aggref = false; + return false; + } + return expression_tree_walker(node, find_cols_walker, + (void *) context); +} + +/* + * (Re-)initialize the hash table(s) to empty. + * + * To implement hashed aggregation, we need a hashtable that stores a + * representative tuple and an array of AggStatePerGroup structs for each + * distinct set of GROUP BY column values. We compute the hash key from the + * GROUP BY columns. The per-group data is allocated in lookup_hash_entry(), + * for each entry. + * + * We have a separate hashtable and associated perhash data structure for each + * grouping set for which we're doing hashing. + * + * The contents of the hash tables always live in the hashcontext's per-tuple + * memory context (there is only one of these for all tables together, since + * they are all reset at the same time). + */ +static void +build_hash_tables(AggState *aggstate) +{ + int setno; + + for (setno = 0; setno < aggstate->num_hashes; ++setno) + { + AggStatePerHash perhash = &aggstate->perhash[setno]; + long nbuckets; + Size memory; + + if (perhash->hashtable != NULL) + { + ResetTupleHashTable(perhash->hashtable); + continue; + } + + Assert(perhash->aggnode->numGroups > 0); + + memory = aggstate->hash_mem_limit / aggstate->num_hashes; + + /* choose reasonable number of buckets per hashtable */ + nbuckets = hash_choose_num_buckets(aggstate->hashentrysize, + perhash->aggnode->numGroups, + memory); + + build_hash_table(aggstate, setno, nbuckets); + } + + aggstate->hash_ngroups_current = 0; +} + +/* + * Build a single hashtable for this grouping set. + */ +static void +build_hash_table(AggState *aggstate, int setno, long nbuckets) +{ + AggStatePerHash perhash = &aggstate->perhash[setno]; + MemoryContext metacxt = aggstate->hash_metacxt; + MemoryContext hashcxt = aggstate->hashcontext->ecxt_per_tuple_memory; + MemoryContext tmpcxt = aggstate->tmpcontext->ecxt_per_tuple_memory; + Size additionalsize; + + Assert(aggstate->aggstrategy == AGG_HASHED || + aggstate->aggstrategy == AGG_MIXED); + + /* + * Used to make sure initial hash table allocation does not exceed + * hash_mem. Note that the estimate does not include space for + * pass-by-reference transition data values, nor for the representative + * tuple of each group. + */ + additionalsize = aggstate->numtrans * sizeof(AggStatePerGroupData); + + perhash->hashtable = BuildTupleHashTableExt(&aggstate->ss.ps, + perhash->hashslot->tts_tupleDescriptor, + perhash->numCols, + perhash->hashGrpColIdxHash, + perhash->eqfuncoids, + perhash->hashfunctions, + perhash->aggnode->grpCollations, + nbuckets, + additionalsize, + metacxt, + hashcxt, + tmpcxt, + DO_AGGSPLIT_SKIPFINAL(aggstate->aggsplit)); +} + +/* + * Compute columns that actually need to be stored in hashtable entries. The + * incoming tuples from the child plan node will contain grouping columns, + * other columns referenced in our targetlist and qual, columns used to + * compute the aggregate functions, and perhaps just junk columns we don't use + * at all. Only columns of the first two types need to be stored in the + * hashtable, and getting rid of the others can make the table entries + * significantly smaller. The hashtable only contains the relevant columns, + * and is packed/unpacked in lookup_hash_entry() / agg_retrieve_hash_table() + * into the format of the normal input descriptor. + * + * Additional columns, in addition to the columns grouped by, come from two + * sources: Firstly functionally dependent columns that we don't need to group + * by themselves, and secondly ctids for row-marks. + * + * To eliminate duplicates, we build a bitmapset of the needed columns, and + * then build an array of the columns included in the hashtable. We might + * still have duplicates if the passed-in grpColIdx has them, which can happen + * in edge cases from semijoins/distinct; these can't always be removed, + * because it's not certain that the duplicate cols will be using the same + * hash function. + * + * Note that the array is preserved over ExecReScanAgg, so we allocate it in + * the per-query context (unlike the hash table itself). + */ +static void +find_hash_columns(AggState *aggstate) +{ + Bitmapset *base_colnos; + Bitmapset *aggregated_colnos; + TupleDesc scanDesc = aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + List *outerTlist = outerPlanState(aggstate)->plan->targetlist; + int numHashes = aggstate->num_hashes; + EState *estate = aggstate->ss.ps.state; + int j; + + /* Find Vars that will be needed in tlist and qual */ + find_cols(aggstate, &aggregated_colnos, &base_colnos); + aggstate->colnos_needed = bms_union(base_colnos, aggregated_colnos); + aggstate->max_colno_needed = 0; + aggstate->all_cols_needed = true; + + for (int i = 0; i < scanDesc->natts; i++) + { + int colno = i + 1; + + if (bms_is_member(colno, aggstate->colnos_needed)) + aggstate->max_colno_needed = colno; + else + aggstate->all_cols_needed = false; + } + + for (j = 0; j < numHashes; ++j) + { + AggStatePerHash perhash = &aggstate->perhash[j]; + Bitmapset *colnos = bms_copy(base_colnos); + AttrNumber *grpColIdx = perhash->aggnode->grpColIdx; + List *hashTlist = NIL; + TupleDesc hashDesc; + int maxCols; + int i; + + perhash->largestGrpColIdx = 0; + + /* + * If we're doing grouping sets, then some Vars might be referenced in + * tlist/qual for the benefit of other grouping sets, but not needed + * when hashing; i.e. prepare_projection_slot will null them out, so + * there'd be no point storing them. Use prepare_projection_slot's + * logic to determine which. + */ + if (aggstate->phases[0].grouped_cols) + { + Bitmapset *grouped_cols = aggstate->phases[0].grouped_cols[j]; + ListCell *lc; + + foreach(lc, aggstate->all_grouped_cols) + { + int attnum = lfirst_int(lc); + + if (!bms_is_member(attnum, grouped_cols)) + colnos = bms_del_member(colnos, attnum); + } + } + + /* + * Compute maximum number of input columns accounting for possible + * duplications in the grpColIdx array, which can happen in some edge + * cases where HashAggregate was generated as part of a semijoin or a + * DISTINCT. + */ + maxCols = bms_num_members(colnos) + perhash->numCols; + + perhash->hashGrpColIdxInput = + palloc(maxCols * sizeof(AttrNumber)); + perhash->hashGrpColIdxHash = + palloc(perhash->numCols * sizeof(AttrNumber)); + + /* Add all the grouping columns to colnos */ + for (i = 0; i < perhash->numCols; i++) + colnos = bms_add_member(colnos, grpColIdx[i]); + + /* + * First build mapping for columns directly hashed. These are the + * first, because they'll be accessed when computing hash values and + * comparing tuples for exact matches. We also build simple mapping + * for execGrouping, so it knows where to find the to-be-hashed / + * compared columns in the input. + */ + for (i = 0; i < perhash->numCols; i++) + { + perhash->hashGrpColIdxInput[i] = grpColIdx[i]; + perhash->hashGrpColIdxHash[i] = i + 1; + perhash->numhashGrpCols++; + /* delete already mapped columns */ + bms_del_member(colnos, grpColIdx[i]); + } + + /* and add the remaining columns */ + while ((i = bms_first_member(colnos)) >= 0) + { + perhash->hashGrpColIdxInput[perhash->numhashGrpCols] = i; + perhash->numhashGrpCols++; + } + + /* and build a tuple descriptor for the hashtable */ + for (i = 0; i < perhash->numhashGrpCols; i++) + { + int varNumber = perhash->hashGrpColIdxInput[i] - 1; + + hashTlist = lappend(hashTlist, list_nth(outerTlist, varNumber)); + perhash->largestGrpColIdx = + Max(varNumber + 1, perhash->largestGrpColIdx); + } + + hashDesc = ExecTypeFromTL(hashTlist); + + execTuplesHashPrepare(perhash->numCols, + perhash->aggnode->grpOperators, + &perhash->eqfuncoids, + &perhash->hashfunctions); + perhash->hashslot = + ExecAllocTableSlot(&estate->es_tupleTable, hashDesc, + &TTSOpsMinimalTuple); + + list_free(hashTlist); + bms_free(colnos); + } + + bms_free(base_colnos); +} + +/* + * Estimate per-hash-table-entry overhead. + */ +Size +hash_agg_entry_size(int numTrans, Size tupleWidth, Size transitionSpace) +{ + Size tupleChunkSize; + Size pergroupChunkSize; + Size transitionChunkSize; + Size tupleSize = (MAXALIGN(SizeofMinimalTupleHeader) + + tupleWidth); + Size pergroupSize = numTrans * sizeof(AggStatePerGroupData); + + tupleChunkSize = CHUNKHDRSZ + tupleSize; + + if (pergroupSize > 0) + pergroupChunkSize = CHUNKHDRSZ + pergroupSize; + else + pergroupChunkSize = 0; + + if (transitionSpace > 0) + transitionChunkSize = CHUNKHDRSZ + transitionSpace; + else + transitionChunkSize = 0; + + return + sizeof(TupleHashEntryData) + + tupleChunkSize + + pergroupChunkSize + + transitionChunkSize; +} + +/* + * hashagg_recompile_expressions() + * + * Identifies the right phase, compiles the right expression given the + * arguments, and then sets phase->evalfunc to that expression. + * + * Different versions of the compiled expression are needed depending on + * whether hash aggregation has spilled or not, and whether it's reading from + * the outer plan or a tape. Before spilling to disk, the expression reads + * from the outer plan and does not need to perform a NULL check. After + * HashAgg begins to spill, new groups will not be created in the hash table, + * and the AggStatePerGroup array may be NULL; therefore we need to add a null + * pointer check to the expression. Then, when reading spilled data from a + * tape, we change the outer slot type to be a fixed minimal tuple slot. + * + * It would be wasteful to recompile every time, so cache the compiled + * expressions in the AggStatePerPhase, and reuse when appropriate. + */ +static void +hashagg_recompile_expressions(AggState *aggstate, bool minslot, bool nullcheck) +{ + AggStatePerPhase phase; + int i = minslot ? 1 : 0; + int j = nullcheck ? 1 : 0; + + Assert(aggstate->aggstrategy == AGG_HASHED || + aggstate->aggstrategy == AGG_MIXED); + + if (aggstate->aggstrategy == AGG_HASHED) + phase = &aggstate->phases[0]; + else /* AGG_MIXED */ + phase = &aggstate->phases[1]; + + if (phase->evaltrans_cache[i][j] == NULL) + { + const TupleTableSlotOps *outerops = aggstate->ss.ps.outerops; + bool outerfixed = aggstate->ss.ps.outeropsfixed; + bool dohash = true; + bool dosort = false; + + /* + * If minslot is true, that means we are processing a spilled batch + * (inside agg_refill_hash_table()), and we must not advance the + * sorted grouping sets. + */ + if (aggstate->aggstrategy == AGG_MIXED && !minslot) + dosort = true; + + /* temporarily change the outerops while compiling the expression */ + if (minslot) + { + aggstate->ss.ps.outerops = &TTSOpsMinimalTuple; + aggstate->ss.ps.outeropsfixed = true; + } + + phase->evaltrans_cache[i][j] = ExecBuildAggTrans(aggstate, phase, + dosort, dohash, + nullcheck); + + /* change back */ + aggstate->ss.ps.outerops = outerops; + aggstate->ss.ps.outeropsfixed = outerfixed; + } + + phase->evaltrans = phase->evaltrans_cache[i][j]; +} + +/* + * Set limits that trigger spilling to avoid exceeding hash_mem. Consider the + * number of partitions we expect to create (if we do spill). + * + * There are two limits: a memory limit, and also an ngroups limit. The + * ngroups limit becomes important when we expect transition values to grow + * substantially larger than the initial value. + */ +void +hash_agg_set_limits(double hashentrysize, double input_groups, int used_bits, + Size *mem_limit, uint64 *ngroups_limit, + int *num_partitions) +{ + int npartitions; + Size partition_mem; + Size hash_mem_limit = get_hash_memory_limit(); + + /* if not expected to spill, use all of hash_mem */ + if (input_groups * hashentrysize <= hash_mem_limit) + { + if (num_partitions != NULL) + *num_partitions = 0; + *mem_limit = hash_mem_limit; + *ngroups_limit = hash_mem_limit / hashentrysize; + return; + } + + /* + * Calculate expected memory requirements for spilling, which is the size + * of the buffers needed for all the tapes that need to be open at once. + * Then, subtract that from the memory available for holding hash tables. + */ + npartitions = hash_choose_num_partitions(input_groups, + hashentrysize, + used_bits, + NULL); + if (num_partitions != NULL) + *num_partitions = npartitions; + + partition_mem = + HASHAGG_READ_BUFFER_SIZE + + HASHAGG_WRITE_BUFFER_SIZE * npartitions; + + /* + * Don't set the limit below 3/4 of hash_mem. In that case, we are at the + * minimum number of partitions, so we aren't going to dramatically exceed + * work mem anyway. + */ + if (hash_mem_limit > 4 * partition_mem) + *mem_limit = hash_mem_limit - partition_mem; + else + *mem_limit = hash_mem_limit * 0.75; + + if (*mem_limit > hashentrysize) + *ngroups_limit = *mem_limit / hashentrysize; + else + *ngroups_limit = 1; +} + +/* + * hash_agg_check_limits + * + * After adding a new group to the hash table, check whether we need to enter + * spill mode. Allocations may happen without adding new groups (for instance, + * if the transition state size grows), so this check is imperfect. + */ +static void +hash_agg_check_limits(AggState *aggstate) +{ + uint64 ngroups = aggstate->hash_ngroups_current; + Size meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, + true); + Size hashkey_mem = MemoryContextMemAllocated(aggstate->hashcontext->ecxt_per_tuple_memory, + true); + + /* + * Don't spill unless there's at least one group in the hash table so we + * can be sure to make progress even in edge cases. + */ + if (aggstate->hash_ngroups_current > 0 && + (meta_mem + hashkey_mem > aggstate->hash_mem_limit || + ngroups > aggstate->hash_ngroups_limit)) + { + hash_agg_enter_spill_mode(aggstate); + } +} + +/* + * Enter "spill mode", meaning that no new groups are added to any of the hash + * tables. Tuples that would create a new group are instead spilled, and + * processed later. + */ +static void +hash_agg_enter_spill_mode(AggState *aggstate) +{ + aggstate->hash_spill_mode = true; + hashagg_recompile_expressions(aggstate, aggstate->table_filled, true); + + if (!aggstate->hash_ever_spilled) + { +#if PG_VERSION_NUM < PG_VERSION_15 + Assert(aggstate->hash_tapeinfo == NULL); +#else + Assert(aggstate->hash_tapeset == NULL); +#endif + Assert(aggstate->hash_spills == NULL); + + aggstate->hash_ever_spilled = true; + +#if PG_VERSION_NUM < PG_VERSION_15 + hashagg_tapeinfo_init(aggstate); +#else + aggstate->hash_tapeset = LogicalTapeSetCreate(true, NULL, -1); +#endif + + aggstate->hash_spills = palloc(sizeof(HashAggSpill) * aggstate->num_hashes); + + for (int setno = 0; setno < aggstate->num_hashes; setno++) + { + AggStatePerHash perhash = &aggstate->perhash[setno]; + HashAggSpill *spill = &aggstate->hash_spills[setno]; + +#if PG_VERSION_NUM < PG_VERSION_15 + hashagg_spill_init(spill, aggstate->hash_tapeinfo, 0, +#else + hashagg_spill_init(spill, aggstate->hash_tapeset, 0, +#endif + perhash->aggnode->numGroups, + aggstate->hashentrysize); + } + } +} + +/* + * Update metrics after filling the hash table. + * + * If reading from the outer plan, from_tape should be false; if reading from + * another tape, from_tape should be true. + */ +static void +hash_agg_update_metrics(AggState *aggstate, bool from_tape, int npartitions) +{ + Size meta_mem; + Size hashkey_mem; + Size buffer_mem; + Size total_mem; + + if (aggstate->aggstrategy != AGG_MIXED && + aggstate->aggstrategy != AGG_HASHED) + return; + + /* memory for the hash table itself */ + meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, true); + + /* memory for the group keys and transition states */ + hashkey_mem = MemoryContextMemAllocated(aggstate->hashcontext->ecxt_per_tuple_memory, true); + + /* memory for read/write tape buffers, if spilled */ + buffer_mem = npartitions * HASHAGG_WRITE_BUFFER_SIZE; + if (from_tape) + buffer_mem += HASHAGG_READ_BUFFER_SIZE; + + /* update peak mem */ + total_mem = meta_mem + hashkey_mem + buffer_mem; + if (total_mem > aggstate->hash_mem_peak) + aggstate->hash_mem_peak = total_mem; + + /* update disk usage */ +#if PG_VERSION_NUM < PG_VERSION_15 + if (aggstate->hash_tapeinfo != NULL) +#else + if (aggstate->hash_tapeset != NULL) +#endif + { +#if PG_VERSION_NUM < PG_VERSION_15 + uint64 disk_used = LogicalTapeSetBlocks(aggstate->hash_tapeinfo->tapeset) * (BLCKSZ / 1024); +#else + uint64 disk_used = LogicalTapeSetBlocks(aggstate->hash_tapeset) * (BLCKSZ / 1024); +#endif + + if (aggstate->hash_disk_used < disk_used) + aggstate->hash_disk_used = disk_used; + } + + /* update hashentrysize estimate based on contents */ + if (aggstate->hash_ngroups_current > 0) + { + aggstate->hashentrysize = + sizeof(TupleHashEntryData) + + (hashkey_mem / (double) aggstate->hash_ngroups_current); + } +} + +/* + * Choose a reasonable number of buckets for the initial hash table size. + */ +static long +hash_choose_num_buckets(double hashentrysize, long ngroups, Size memory) +{ + long max_nbuckets; + long nbuckets = ngroups; + + max_nbuckets = memory / hashentrysize; + + /* + * Underestimating is better than overestimating. Too many buckets crowd + * out space for group keys and transition state values. + */ + max_nbuckets >>= 1; + + if (nbuckets > max_nbuckets) + nbuckets = max_nbuckets; + + return Max(nbuckets, 1); +} + +/* + * Determine the number of partitions to create when spilling, which will + * always be a power of two. If log2_npartitions is non-NULL, set + * *log2_npartitions to the log2() of the number of partitions. + */ +static int +hash_choose_num_partitions(double input_groups, double hashentrysize, + int used_bits, int *log2_npartitions) +{ + Size hash_mem_limit = get_hash_memory_limit(); + double partition_limit; + double mem_wanted; + double dpartitions; + int npartitions; + int partition_bits; + + /* + * Avoid creating so many partitions that the memory requirements of the + * open partition files are greater than 1/4 of hash_mem. + */ + partition_limit = + (hash_mem_limit * 0.25 - HASHAGG_READ_BUFFER_SIZE) / + HASHAGG_WRITE_BUFFER_SIZE; + + mem_wanted = HASHAGG_PARTITION_FACTOR * input_groups * hashentrysize; + + /* make enough partitions so that each one is likely to fit in memory */ + dpartitions = 1 + (mem_wanted / hash_mem_limit); + + if (dpartitions > partition_limit) + dpartitions = partition_limit; + + if (dpartitions < HASHAGG_MIN_PARTITIONS) + dpartitions = HASHAGG_MIN_PARTITIONS; + if (dpartitions > HASHAGG_MAX_PARTITIONS) + dpartitions = HASHAGG_MAX_PARTITIONS; + + /* HASHAGG_MAX_PARTITIONS limit makes this safe */ + npartitions = (int) dpartitions; + + /* ceil(log2(npartitions)) */ + partition_bits = my_log2(npartitions); + + /* make sure that we don't exhaust the hash bits */ + if (partition_bits + used_bits >= 32) + partition_bits = 32 - used_bits; + + if (log2_npartitions != NULL) + *log2_npartitions = partition_bits; + + /* number of partitions will be a power of two */ + npartitions = 1 << partition_bits; + + return npartitions; +} + +/* + * Initialize a freshly-created TupleHashEntry. + */ +static void +initialize_hash_entry(AggState *aggstate, TupleHashTable hashtable, + TupleHashEntry entry) +{ + AggStatePerGroup pergroup; + int transno; + + aggstate->hash_ngroups_current++; + hash_agg_check_limits(aggstate); + + /* no need to allocate or initialize per-group state */ + if (aggstate->numtrans == 0) + return; + + pergroup = (AggStatePerGroup) + MemoryContextAlloc(hashtable->tablecxt, + sizeof(AggStatePerGroupData) * aggstate->numtrans); + + entry->additional = pergroup; + + /* + * Initialize aggregates for new tuple group, lookup_hash_entries() + * already has selected the relevant grouping set. + */ + for (transno = 0; transno < aggstate->numtrans; transno++) + { + AggStatePerTrans pertrans = &aggstate->pertrans[transno]; + AggStatePerGroup pergroupstate = &pergroup[transno]; + + initialize_aggregate(aggstate, pertrans, pergroupstate); + } +} + +/* + * Look up hash entries for the current tuple in all hashed grouping sets. + * + * Be aware that lookup_hash_entry can reset the tmpcontext. + * + * Some entries may be left NULL if we are in "spill mode". The same tuple + * will belong to different groups for each grouping set, so may match a group + * already in memory for one set and match a group not in memory for another + * set. When in "spill mode", the tuple will be spilled for each grouping set + * where it doesn't match a group in memory. + * + * NB: It's possible to spill the same tuple for several different grouping + * sets. This may seem wasteful, but it's actually a trade-off: if we spill + * the tuple multiple times for multiple grouping sets, it can be partitioned + * for each grouping set, making the refilling of the hash table very + * efficient. + */ +static void +lookup_hash_entries(AggState *aggstate) +{ + AggStatePerGroup *pergroup = aggstate->hash_pergroup; + TupleTableSlot *outerslot = aggstate->tmpcontext->ecxt_outertuple; + int setno; + + for (setno = 0; setno < aggstate->num_hashes; setno++) + { + AggStatePerHash perhash = &aggstate->perhash[setno]; + TupleHashTable hashtable = perhash->hashtable; + TupleTableSlot *hashslot = perhash->hashslot; + TupleHashEntry entry; + uint32 hash; + bool isnew = false; + bool *p_isnew; + + /* if hash table already spilled, don't create new entries */ + p_isnew = aggstate->hash_spill_mode ? NULL : &isnew; + + select_current_set(aggstate, setno, true); + prepare_hash_slot(perhash, + outerslot, + hashslot); + + entry = LookupTupleHashEntry(hashtable, hashslot, + p_isnew, &hash); + + if (entry != NULL) + { + if (isnew) + initialize_hash_entry(aggstate, hashtable, entry); + pergroup[setno] = entry->additional; + } + else + { + HashAggSpill *spill = &aggstate->hash_spills[setno]; + TupleTableSlot *slot = aggstate->tmpcontext->ecxt_outertuple; + + if (spill->partitions == NULL) +#if PG_VERSION_NUM < PG_VERSION_15 + hashagg_spill_init(spill, aggstate->hash_tapeinfo, 0, +#else + hashagg_spill_init(spill, aggstate->hash_tapeset, 0, +#endif + perhash->aggnode->numGroups, + aggstate->hashentrysize); + + hashagg_spill_tuple(aggstate, spill, slot, hash); + pergroup[setno] = NULL; + } + } +} + +/* + * ExecAgg - + * + * ExecAgg receives tuples from its outer subplan and aggregates over + * the appropriate attribute for each aggregate function use (Aggref + * node) appearing in the targetlist or qual of the node. The number + * of tuples to aggregate over depends on whether grouped or plain + * aggregation is selected. In grouped aggregation, we produce a result + * row for each group; in plain aggregation there's a single result row + * for the whole query. In either case, the value of each aggregate is + * stored in the expression context to be used when ExecProject evaluates + * the result tuple. + */ +static TupleTableSlot * +ExecAgg(PlanState *pstate) +{ + VectorAggState *vectoraggstate = (VectorAggState *) pstate; + AggState *node = vectoraggstate->aggstate; + TupleTableSlot *result = NULL; + + CHECK_FOR_INTERRUPTS(); + + if (!node->agg_done) + { + /* Dispatch based on strategy */ + switch (node->phase->aggstrategy) + { + case AGG_HASHED: + if (!node->table_filled) + agg_fill_hash_table(node); + /* FALLTHROUGH */ + case AGG_MIXED: + result = agg_retrieve_hash_table(node); + break; + case AGG_PLAIN: + case AGG_SORTED: + result = agg_retrieve_direct(vectoraggstate); + break; + } + + if (!TupIsNull(result)) + return result; + } + + return NULL; +} + +/* + * ExecAgg for non-hashed case + */ +static TupleTableSlot * +agg_retrieve_direct(VectorAggState *vectoraggstate) +{ +#if PG_VERSION_NUM < PG_VERSION_15 + AggState *aggstate = vectoraggstate->aggstate; +#else + AggState *aggstate = vectoraggstate->aggstate; +#endif + Agg *node = aggstate->phase->aggnode; + ExprContext *econtext; + ExprContext *tmpcontext; + AggStatePerAgg peragg; + AggStatePerGroup *pergroups; + TupleTableSlot *outerslot = NULL; + TupleTableSlot *firstSlot; + TupleTableSlot *result; + bool hasGroupingSets = aggstate->phase->numsets > 0; + int numGroupingSets = Max(aggstate->phase->numsets, 1); + int currentSet; + int nextSetSize; + int numReset; + int i; + + /* + * get state info from node + * + * econtext is the per-output-tuple expression context + * + * tmpcontext is the per-input-tuple expression context + */ + econtext = aggstate->ss.ps.ps_ExprContext; + tmpcontext = aggstate->tmpcontext; + + peragg = aggstate->peragg; + pergroups = aggstate->pergroups; + firstSlot = aggstate->ss.ss_ScanTupleSlot; + + /* + * We loop retrieving groups until we find one matching + * aggstate->ss.ps.qual + * + * For grouping sets, we have the invariant that aggstate->projected_set + * is either -1 (initial call) or the index (starting from 0) in + * gset_lengths for the group we just completed (either by projecting a + * row or by discarding it in the qual). + */ + while (!aggstate->agg_done) + { + /* + * Clear the per-output-tuple context for each group, as well as + * aggcontext (which contains any pass-by-ref transvalues of the old + * group). Some aggregate functions store working state in child + * contexts; those now get reset automatically without us needing to + * do anything special. + * + * We use ReScanExprContext not just ResetExprContext because we want + * any registered shutdown callbacks to be called. That allows + * aggregate functions to ensure they've cleaned up any non-memory + * resources. + */ + ReScanExprContext(econtext); + + /* + * Determine how many grouping sets need to be reset at this boundary. + */ + if (aggstate->projected_set >= 0 && + aggstate->projected_set < numGroupingSets) + numReset = aggstate->projected_set + 1; + else + numReset = numGroupingSets; + + /* + * numReset can change on a phase boundary, but that's OK; we want to + * reset the contexts used in _this_ phase, and later, after possibly + * changing phase, initialize the right number of aggregates for the + * _new_ phase. + */ + + for (i = 0; i < numReset; i++) + { + ReScanExprContext(aggstate->aggcontexts[i]); + } + + /* + * Check if input is complete and there are no more groups to project + * in this phase; move to next phase or mark as done. + */ + if (aggstate->input_done == true && + aggstate->projected_set >= (numGroupingSets - 1)) + { + if (aggstate->current_phase < aggstate->numphases - 1) + { + initialize_phase(aggstate, aggstate->current_phase + 1); + aggstate->input_done = false; + aggstate->projected_set = -1; + numGroupingSets = Max(aggstate->phase->numsets, 1); + node = aggstate->phase->aggnode; + numReset = numGroupingSets; + } + else if (aggstate->aggstrategy == AGG_MIXED) + { + /* + * Mixed mode; we've output all the grouped stuff and have + * full hashtables, so switch to outputting those. + */ + initialize_phase(aggstate, 0); + aggstate->table_filled = true; + ResetTupleHashIterator(aggstate->perhash[0].hashtable, + &aggstate->perhash[0].hashiter); + select_current_set(aggstate, 0, true); + return agg_retrieve_hash_table(aggstate); + } + else + { + aggstate->agg_done = true; + break; + } + } + + /* + * Get the number of columns in the next grouping set after the last + * projected one (if any). This is the number of columns to compare to + * see if we reached the boundary of that set too. + */ + if (aggstate->projected_set >= 0 && + aggstate->projected_set < (numGroupingSets - 1)) + nextSetSize = aggstate->phase->gset_lengths[aggstate->projected_set + 1]; + else + nextSetSize = 0; + + /*---------- + * If a subgroup for the current grouping set is present, project it. + * + * We have a new group if: + * - we're out of input but haven't projected all grouping sets + * (checked above) + * OR + * - we already projected a row that wasn't from the last grouping + * set + * AND + * - the next grouping set has at least one grouping column (since + * empty grouping sets project only once input is exhausted) + * AND + * - the previous and pending rows differ on the grouping columns + * of the next grouping set + *---------- + */ + tmpcontext->ecxt_innertuple = econtext->ecxt_outertuple; + if (aggstate->input_done || + (node->aggstrategy != AGG_PLAIN && + aggstate->projected_set != -1 && + aggstate->projected_set < (numGroupingSets - 1) && + nextSetSize > 0 && + !ExecQualAndReset(aggstate->phase->eqfunctions[nextSetSize - 1], + tmpcontext))) + { + aggstate->projected_set += 1; + + Assert(aggstate->projected_set < numGroupingSets); + Assert(nextSetSize > 0 || aggstate->input_done); + } + else + { + /* + * We no longer care what group we just projected, the next + * projection will always be the first (or only) grouping set + * (unless the input proves to be empty). + */ + aggstate->projected_set = 0; + + /* + * If we don't already have the first tuple of the new group, + * fetch it from the outer plan. + */ + if (aggstate->grp_firstTuple == NULL) + { + outerslot = fetch_input_tuple(aggstate); + if (!TupIsNull(outerslot)) + { + /* + * Make a copy of the first input tuple; we will use this + * for comparisons (in group mode) and for projection. + */ + aggstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot); + } + else + { + /* outer plan produced no tuples at all */ + if (hasGroupingSets) + { + /* + * If there was no input at all, we need to project + * rows only if there are grouping sets of size 0. + * Note that this implies that there can't be any + * references to ungrouped Vars, which would otherwise + * cause issues with the empty output slot. + * + * XXX: This is no longer true, we currently deal with + * this in finalize_aggregates(). + */ + aggstate->input_done = true; + + while (aggstate->phase->gset_lengths[aggstate->projected_set] > 0) + { + aggstate->projected_set += 1; + if (aggstate->projected_set >= numGroupingSets) + { + /* + * We can't set agg_done here because we might + * have more phases to do, even though the + * input is empty. So we need to restart the + * whole outer loop. + */ + break; + } + } + + if (aggstate->projected_set >= numGroupingSets) + continue; + } + else + { + aggstate->agg_done = true; + /* If we are grouping, we should produce no tuples too */ + if (node->aggstrategy != AGG_PLAIN) + return NULL; + } + } + } + + /* + * Initialize working state for a new input tuple group. + */ + initialize_aggregates(aggstate, pergroups, numReset); + + if (aggstate->grp_firstTuple != NULL) + { + /* + * Store the copied first input tuple in the tuple table slot + * reserved for it. The tuple will be deleted when it is + * cleared from the slot. + */ + ExecForceStoreHeapTuple(aggstate->grp_firstTuple, + firstSlot, true); + aggstate->grp_firstTuple = NULL; /* don't keep two pointers */ + + /* set up for first advance_aggregates call */ + tmpcontext->ecxt_outertuple = outerslot; + + /* + * Process each outer-plan tuple, and then fetch the next one, + * until we exhaust the outer plan or cross a group boundary. + */ + for (;;) + { + /* + * During phase 1 only of a mixed agg, we need to update + * hashtables as well in advance_aggregates. + */ + if (aggstate->aggstrategy == AGG_MIXED && + aggstate->current_phase == 1) + { + lookup_hash_entries(aggstate); + } + + /* Advance the aggregates (or combine functions) */ + advance_aggregates(aggstate); + + int transno; + + currentSet = aggstate->projected_set; + + prepare_projection_slot(aggstate, econtext->ecxt_outertuple, currentSet); + + select_current_set(aggstate, currentSet, false); + + for (transno = 0; transno < aggstate->numtrans; transno++) + { + AggStatePerTrans pertrans = &aggstate->pertrans[transno]; + AggStatePerGroup pergroupstate; + + pergroupstate = &pergroups[currentSet][transno]; + + // Should handle COUNT(*) + if (pertrans->aggref->aggstar) + { + int slotDim = ((VectorTupleTableSlot *)outerslot)->dimension; + pergroupstate->transValue += slotDim; + } + } + + /* Reset per-input-tuple context after each tuple */ + ResetExprContext(tmpcontext); + + outerslot = fetch_input_tuple(aggstate); + if (TupIsNull(outerslot)) + { + /* no more outer-plan tuples available */ + + /* if we built hash tables, finalize any spills */ + if (aggstate->aggstrategy == AGG_MIXED && + aggstate->current_phase == 1) + hashagg_finish_initial_spills(aggstate); + + if (hasGroupingSets) + { + aggstate->input_done = true; + break; + } + else + { + aggstate->agg_done = true; + break; + } + } + /* set up for next advance_aggregates call */ + tmpcontext->ecxt_outertuple = outerslot; + + /* + * If we are grouping, check whether we've crossed a group + * boundary. + */ + if (node->aggstrategy != AGG_PLAIN) + { + tmpcontext->ecxt_innertuple = firstSlot; + if (!ExecQual(aggstate->phase->eqfunctions[node->numCols - 1], + tmpcontext)) + { + aggstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot); + break; + } + } + } + } + + /* + * Use the representative input tuple for any references to + * non-aggregated input columns in aggregate direct args, the node + * qual, and the tlist. (If we are not grouping, and there are no + * input rows at all, we will come here with an empty firstSlot + * ... but if not grouping, there can't be any references to + * non-aggregated input columns, so no problem.) + */ + econtext->ecxt_outertuple = firstSlot; + } + + Assert(aggstate->projected_set >= 0); + + currentSet = aggstate->projected_set; + + prepare_projection_slot(aggstate, econtext->ecxt_outertuple, currentSet); + + select_current_set(aggstate, currentSet, false); + + finalize_aggregates(aggstate, + peragg, + pergroups[currentSet]); + + /* + * If there's no row to project right now, we must continue rather + * than returning a null since there might be more groups. + */ + result = project_aggregates(aggstate); + if (result) + return result; + } + + /* No more groups */ + return NULL; +} + +/* + * ExecAgg for hashed case: read input and build hash table + */ +static void +agg_fill_hash_table(AggState *aggstate) +{ + TupleTableSlot *outerslot; + ExprContext *tmpcontext = aggstate->tmpcontext; + + /* + * Process each outer-plan tuple, and then fetch the next one, until we + * exhaust the outer plan. + */ + for (;;) + { + outerslot = fetch_input_tuple(aggstate); + if (TupIsNull(outerslot)) + break; + + /* set up for lookup_hash_entries and advance_aggregates */ + tmpcontext->ecxt_outertuple = outerslot; + + /* Find or build hashtable entries */ + lookup_hash_entries(aggstate); + + /* Advance the aggregates (or combine functions) */ + advance_aggregates(aggstate); + + /* + * Reset per-input-tuple context after each tuple, but note that the + * hash lookups do this too + */ + ResetExprContext(aggstate->tmpcontext); + } + + /* finalize spills, if any */ + hashagg_finish_initial_spills(aggstate); + + aggstate->table_filled = true; + /* Initialize to walk the first hash table */ + select_current_set(aggstate, 0, true); + ResetTupleHashIterator(aggstate->perhash[0].hashtable, + &aggstate->perhash[0].hashiter); +} + +/* + * If any data was spilled during hash aggregation, reset the hash table and + * reprocess one batch of spilled data. After reprocessing a batch, the hash + * table will again contain data, ready to be consumed by + * agg_retrieve_hash_table_in_memory(). + * + * Should only be called after all in memory hash table entries have been + * finalized and emitted. + * + * Return false when input is exhausted and there's no more work to be done; + * otherwise return true. + */ +static bool +agg_refill_hash_table(AggState *aggstate) +{ + HashAggBatch *batch; + AggStatePerHash perhash; + HashAggSpill spill; +#if PG_VERSION_NUM < PG_VERSION_15 + HashTapeInfo *tapeinfo = aggstate->hash_tapeinfo; +#else + LogicalTapeSet *tapeset = aggstate->hash_tapeset; +#endif + bool spill_initialized = false; + + if (aggstate->hash_batches == NIL) + return false; + + /* hash_batches is a stack, with the top item at the end of the list */ + batch = llast(aggstate->hash_batches); + aggstate->hash_batches = list_delete_last(aggstate->hash_batches); + + hash_agg_set_limits(aggstate->hashentrysize, batch->input_card, + batch->used_bits, &aggstate->hash_mem_limit, + &aggstate->hash_ngroups_limit, NULL); + + /* + * Each batch only processes one grouping set; set the rest to NULL so + * that advance_aggregates() knows to ignore them. We don't touch + * pergroups for sorted grouping sets here, because they will be needed if + * we rescan later. The expressions for sorted grouping sets will not be + * evaluated after we recompile anyway. + */ + MemSet(aggstate->hash_pergroup, 0, + sizeof(AggStatePerGroup) * aggstate->num_hashes); + + /* free memory and reset hash tables */ + ReScanExprContext(aggstate->hashcontext); + for (int setno = 0; setno < aggstate->num_hashes; setno++) + ResetTupleHashTable(aggstate->perhash[setno].hashtable); + + aggstate->hash_ngroups_current = 0; + + /* + * In AGG_MIXED mode, hash aggregation happens in phase 1 and the output + * happens in phase 0. So, we switch to phase 1 when processing a batch, + * and back to phase 0 after the batch is done. + */ + Assert(aggstate->current_phase == 0); + if (aggstate->phase->aggstrategy == AGG_MIXED) + { + aggstate->current_phase = 1; + aggstate->phase = &aggstate->phases[aggstate->current_phase]; + } + + select_current_set(aggstate, batch->setno, true); + + perhash = &aggstate->perhash[aggstate->current_set]; + + /* + * Spilled tuples are always read back as MinimalTuples, which may be + * different from the outer plan, so recompile the aggregate expressions. + * + * We still need the NULL check, because we are only processing one + * grouping set at a time and the rest will be NULL. + */ + hashagg_recompile_expressions(aggstate, true, true); + + for (;;) + { + TupleTableSlot *spillslot = aggstate->hash_spill_rslot; + TupleTableSlot *hashslot = perhash->hashslot; + TupleHashEntry entry; + MinimalTuple tuple; + uint32 hash; + bool isnew = false; + bool *p_isnew = aggstate->hash_spill_mode ? NULL : &isnew; + + CHECK_FOR_INTERRUPTS(); + + tuple = hashagg_batch_read(batch, &hash); + if (tuple == NULL) + break; + + ExecStoreMinimalTuple(tuple, spillslot, true); + aggstate->tmpcontext->ecxt_outertuple = spillslot; + + prepare_hash_slot(perhash, + aggstate->tmpcontext->ecxt_outertuple, + hashslot); +#if PG_VERSION_NUM < PG_VERSION_15 + entry = LookupTupleHashEntryHash( + perhash->hashtable, hashslot, p_isnew, hash); +#else + entry = LookupTupleHashEntryHash(perhash->hashtable, hashslot, + p_isnew, hash); +#endif + + if (entry != NULL) + { + if (isnew) + initialize_hash_entry(aggstate, perhash->hashtable, entry); + aggstate->hash_pergroup[batch->setno] = entry->additional; + advance_aggregates(aggstate); + } + else + { + if (!spill_initialized) + { + /* + * Avoid initializing the spill until we actually need it so + * that we don't assign tapes that will never be used. + */ + spill_initialized = true; +#if PG_VERSION_NUM < PG_VERSION_15 + hashagg_spill_init(&spill, tapeinfo, batch->used_bits, +#else + hashagg_spill_init(&spill, tapeset, batch->used_bits, +#endif + batch->input_card, aggstate->hashentrysize); + } + /* no memory for a new group, spill */ + hashagg_spill_tuple(aggstate, &spill, spillslot, hash); + + aggstate->hash_pergroup[batch->setno] = NULL; + } + + /* + * Reset per-input-tuple context after each tuple, but note that the + * hash lookups do this too + */ + ResetExprContext(aggstate->tmpcontext); + } + +#if PG_VERSION_NUM < PG_VERSION_15 + hashagg_tapeinfo_release(tapeinfo, batch->input_tapenum); +#else + LogicalTapeClose(batch->input_tape); +#endif + + /* change back to phase 0 */ + aggstate->current_phase = 0; + aggstate->phase = &aggstate->phases[aggstate->current_phase]; + + if (spill_initialized) + { + hashagg_spill_finish(aggstate, &spill, batch->setno); + hash_agg_update_metrics(aggstate, true, spill.npartitions); + } + else + hash_agg_update_metrics(aggstate, true, 0); + + aggstate->hash_spill_mode = false; + + /* prepare to walk the first hash table */ + select_current_set(aggstate, batch->setno, true); + ResetTupleHashIterator(aggstate->perhash[batch->setno].hashtable, + &aggstate->perhash[batch->setno].hashiter); + + pfree(batch); + + return true; +} + +/* + * ExecAgg for hashed case: retrieving groups from hash table + * + * After exhausting in-memory tuples, also try refilling the hash table using + * previously-spilled tuples. Only returns NULL after all in-memory and + * spilled tuples are exhausted. + */ +static TupleTableSlot * +agg_retrieve_hash_table(AggState *aggstate) +{ + TupleTableSlot *result = NULL; + + while (result == NULL) + { + result = agg_retrieve_hash_table_in_memory(aggstate); + if (result == NULL) + { + if (!agg_refill_hash_table(aggstate)) + { + aggstate->agg_done = true; + break; + } + } + } + + return result; +} + +/* + * Retrieve the groups from the in-memory hash tables without considering any + * spilled tuples. + */ +static TupleTableSlot * +agg_retrieve_hash_table_in_memory(AggState *aggstate) +{ + ExprContext *econtext; + AggStatePerAgg peragg; + AggStatePerGroup pergroup; + TupleHashEntryData *entry; + TupleTableSlot *firstSlot; + TupleTableSlot *result; + AggStatePerHash perhash; + + /* + * get state info from node. + * + * econtext is the per-output-tuple expression context. + */ + econtext = aggstate->ss.ps.ps_ExprContext; + peragg = aggstate->peragg; + firstSlot = aggstate->ss.ss_ScanTupleSlot; + + /* + * Note that perhash (and therefore anything accessed through it) can + * change inside the loop, as we change between grouping sets. + */ + perhash = &aggstate->perhash[aggstate->current_set]; + + /* + * We loop retrieving groups until we find one satisfying + * aggstate->ss.ps.qual + */ + for (;;) + { + TupleTableSlot *hashslot = perhash->hashslot; + int i; + + CHECK_FOR_INTERRUPTS(); + + /* + * Find the next entry in the hash table + */ + entry = ScanTupleHashTable(perhash->hashtable, &perhash->hashiter); + if (entry == NULL) + { + int nextset = aggstate->current_set + 1; + + if (nextset < aggstate->num_hashes) + { + /* + * Switch to next grouping set, reinitialize, and restart the + * loop. + */ + select_current_set(aggstate, nextset, true); + + perhash = &aggstate->perhash[aggstate->current_set]; + + ResetTupleHashIterator(perhash->hashtable, &perhash->hashiter); + + continue; + } + else + { + return NULL; + } + } + + /* + * Clear the per-output-tuple context for each group + * + * We intentionally don't use ReScanExprContext here; if any aggs have + * registered shutdown callbacks, they mustn't be called yet, since we + * might not be done with that agg. + */ + ResetExprContext(econtext); + + /* + * Transform representative tuple back into one with the right + * columns. + */ + ExecStoreMinimalTuple(entry->firstTuple, hashslot, false); + slot_getallattrs(hashslot); + + ExecClearTuple(firstSlot); + memset(firstSlot->tts_isnull, true, + firstSlot->tts_tupleDescriptor->natts * sizeof(bool)); + + for (i = 0; i < perhash->numhashGrpCols; i++) + { + int varNumber = perhash->hashGrpColIdxInput[i] - 1; + + firstSlot->tts_values[varNumber] = hashslot->tts_values[i]; + firstSlot->tts_isnull[varNumber] = hashslot->tts_isnull[i]; + } + ExecStoreVirtualTuple(firstSlot); + + pergroup = (AggStatePerGroup) entry->additional; + + /* + * Use the representative input tuple for any references to + * non-aggregated input columns in the qual and tlist. + */ + econtext->ecxt_outertuple = firstSlot; + + prepare_projection_slot(aggstate, + econtext->ecxt_outertuple, + aggstate->current_set); + + finalize_aggregates(aggstate, peragg, pergroup); + + result = project_aggregates(aggstate); + if (result) + return result; + } + + /* No more groups */ + return NULL; +} + +#if PG_VERSION_NUM < PG_VERSION_15 +/* + * Initialize HashTapeInfo + */ +static void +hashagg_tapeinfo_init(AggState *aggstate) +{ + HashTapeInfo *tapeinfo = palloc(sizeof(HashTapeInfo)); + int init_tapes = 16; /* expanded dynamically */ + + tapeinfo->tapeset = LogicalTapeSetCreate(init_tapes, true, NULL, NULL, -1); + tapeinfo->ntapes = init_tapes; + tapeinfo->nfreetapes = init_tapes; + tapeinfo->freetapes_alloc = init_tapes; + tapeinfo->freetapes = palloc(init_tapes * sizeof(int)); + for (int i = 0; i < init_tapes; i++) + tapeinfo->freetapes[i] = i; + + aggstate->hash_tapeinfo = tapeinfo; +} + +/* + * Assign unused tapes to spill partitions, extending the tape set if + * necessary. + */ +static void +hashagg_tapeinfo_assign(HashTapeInfo *tapeinfo, int *partitions, + int npartitions) +{ + int partidx = 0; + + /* use free tapes if available */ + while (partidx < npartitions && tapeinfo->nfreetapes > 0) + partitions[partidx++] = tapeinfo->freetapes[--tapeinfo->nfreetapes]; + + if (partidx < npartitions) + { + LogicalTapeSetExtend(tapeinfo->tapeset, npartitions - partidx); + + while (partidx < npartitions) + partitions[partidx++] = tapeinfo->ntapes++; + } +} + +/* + * After a tape has already been written to and then read, this function + * rewinds it for writing and adds it to the free list. + */ +static void +hashagg_tapeinfo_release(HashTapeInfo *tapeinfo, int tapenum) +{ + /* rewinding frees the buffer while not in use */ + LogicalTapeRewindForWrite(tapeinfo->tapeset, tapenum); + if (tapeinfo->freetapes_alloc == tapeinfo->nfreetapes) + { + tapeinfo->freetapes_alloc <<= 1; + tapeinfo->freetapes = repalloc(tapeinfo->freetapes, + tapeinfo->freetapes_alloc * sizeof(int)); + } + tapeinfo->freetapes[tapeinfo->nfreetapes++] = tapenum; +} +#endif + +/* + * hashagg_spill_init + * + * Called after we determined that spilling is necessary. Chooses the number + * of partitions to create, and initializes them. + */ +static void +#if PG_VERSION_NUM < PG_VERSION_15 +hashagg_spill_init(HashAggSpill *spill, HashTapeInfo *tapeinfo, int used_bits, +#else +hashagg_spill_init(HashAggSpill *spill, LogicalTapeSet *tapeset, int used_bits, +#endif + double input_groups, double hashentrysize) +{ + int npartitions; + int partition_bits; + + npartitions = hash_choose_num_partitions(input_groups, hashentrysize, + used_bits, &partition_bits); + +#if PG_VERSION_NUM < PG_VERSION_15 + spill->partitions = palloc0(sizeof(int) * npartitions); +#else + spill->partitions = palloc0(sizeof(LogicalTape *) * npartitions); +#endif + spill->ntuples = palloc0(sizeof(int64) * npartitions); + spill->hll_card = palloc0(sizeof(hyperLogLogState) * npartitions); + +#if PG_VERSION_NUM <= PG_VERSION_15 + hashagg_tapeinfo_assign(tapeinfo, spill->partitions, npartitions); +#else + for (int i = 0; i < npartitions; i++) + spill->partitions[i] = LogicalTapeCreate(tapeset); +#endif + +#if PG_VERSION_NUM < PG_VERSION_15 + spill->tapeset = tapeinfo->tapeset; +#endif + spill->shift = 32 - used_bits - partition_bits; + spill->mask = (npartitions - 1) << spill->shift; + spill->npartitions = npartitions; + + for (int i = 0; i < npartitions; i++) + initHyperLogLog(&spill->hll_card[i], HASHAGG_HLL_BIT_WIDTH); +} + +/* + * hashagg_spill_tuple + * + * No room for new groups in the hash table. Save for later in the appropriate + * partition. + */ +static Size +hashagg_spill_tuple(AggState *aggstate, HashAggSpill *spill, + TupleTableSlot *inputslot, uint32 hash) +{ +#if PG_VERSION_NUM <= PG_VERSION_15 + LogicalTapeSet *tapeset = spill->tapeset; +#endif + TupleTableSlot *spillslot; + int partition; + MinimalTuple tuple; +#if PG_VERSION_NUM < PG_VERSION_15 + int tapenum; +#else + LogicalTape *tape; +#endif + int total_written = 0; + bool shouldFree; + + Assert(spill->partitions != NULL); + + /* spill only attributes that we actually need */ + if (!aggstate->all_cols_needed) + { + spillslot = aggstate->hash_spill_wslot; + slot_getsomeattrs(inputslot, aggstate->max_colno_needed); + ExecClearTuple(spillslot); + for (int i = 0; i < spillslot->tts_tupleDescriptor->natts; i++) + { + if (bms_is_member(i + 1, aggstate->colnos_needed)) + { + spillslot->tts_values[i] = inputslot->tts_values[i]; + spillslot->tts_isnull[i] = inputslot->tts_isnull[i]; + } + else + spillslot->tts_isnull[i] = true; + } + ExecStoreVirtualTuple(spillslot); + } + else + spillslot = inputslot; + + tuple = ExecFetchSlotMinimalTuple(spillslot, &shouldFree); + + partition = (hash & spill->mask) >> spill->shift; + spill->ntuples[partition]++; + + /* + * All hash values destined for a given partition have some bits in + * common, which causes bad HLL cardinality estimates. Hash the hash to + * get a more uniform distribution. + */ + addHyperLogLog(&spill->hll_card[partition], hash_bytes_uint32(hash)); + +#if PG_VERSION_NUM <= PG_VERSION_15 + tapenum = spill->partitions[partition]; +#else + tape = spill->partitions[partition]; +#endif + +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeWrite(tapeset, tapenum, (void *) &hash, sizeof(uint32)); +#else + LogicalTapeWrite(tape, (void *) &hash, sizeof(uint32)); +#endif + total_written += sizeof(uint32); + +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeWrite(tapeset, tapenum, (void *) tuple, tuple->t_len); +#else + LogicalTapeWrite(tape, (void *) tuple, tuple->t_len); +#endif + total_written += tuple->t_len; + + if (shouldFree) + pfree(tuple); + + return total_written; +} + +/* + * hashagg_batch_new + * + * Construct a HashAggBatch item, which represents one iteration of HashAgg to + * be done. + */ +static HashAggBatch * +#if PG_VERSION_NUM < PG_VERSION_15 +hashagg_batch_new(LogicalTapeSet *tapeset, int tapenum, int setno, +#else +hashagg_batch_new(LogicalTape *input_tape, int setno, +#endif + int64 input_tuples, double input_card, int used_bits) +{ + HashAggBatch *batch = palloc0(sizeof(HashAggBatch)); + + batch->setno = setno; + batch->used_bits = used_bits; +#if PG_VERSION_NUM < PG_VERSION_15 + batch->tapeset = tapeset; + batch->input_tapenum = tapenum; +#else + batch->input_tape = input_tape; +#endif + batch->input_tuples = input_tuples; + batch->input_card = input_card; + + return batch; +} + +/* + * read_spilled_tuple + * read the next tuple from a batch's tape. Return NULL if no more. + */ +static MinimalTuple +hashagg_batch_read(HashAggBatch *batch, uint32 *hashp) +{ +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeSet *tapeset = batch->tapeset; + int tapenum = batch->input_tapenum; +#else + LogicalTape *tape = batch->input_tape; +#endif + MinimalTuple tuple; + uint32 t_len; + size_t nread; + uint32 hash; + +#if PG_VERSION_NUM < PG_VERSION_15 + nread = LogicalTapeRead(tapeset, tapenum, &hash, sizeof(uint32)); +#else + nread = LogicalTapeRead(tape, &hash, sizeof(uint32)); +#endif + if (nread == 0) + return NULL; + if (nread != sizeof(uint32)) + ereport(ERROR, + (errcode_for_file_access(), +#if PG_VERSION_NUM < PG_VERSION_15 + errmsg("unexpected EOF for tape %d: requested %zu bytes, read %zu bytes", + tapenum, sizeof(uint32), nread))); +#else + errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes", + tape, sizeof(uint32), nread))); +#endif + if (hashp != NULL) + *hashp = hash; + +#if PG_VERSION_NUM < PG_VERSION_15 + nread = LogicalTapeRead(tapeset, tapenum, &t_len, sizeof(t_len)); +#else + nread = LogicalTapeRead(tape, &t_len, sizeof(t_len)); +#endif + if (nread != sizeof(uint32)) + ereport(ERROR, + (errcode_for_file_access(), +#if PG_VERSION_NUM < PG_VERSION_15 + errmsg("unexpected EOF for tape %d: requested %zu bytes, read %zu bytes", + tapenum, sizeof(uint32), nread))); +#else + errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes", + tape, sizeof(uint32), nread))); +#endif + + tuple = (MinimalTuple) palloc(t_len); + tuple->t_len = t_len; + +#if PG_VERSION_NUM < PG_VERSION_15 + nread = LogicalTapeRead(tapeset, tapenum, +#else + nread = LogicalTapeRead(tape, +#endif + (void *) ((char *) tuple + sizeof(uint32)), + t_len - sizeof(uint32)); + if (nread != t_len - sizeof(uint32)) + ereport(ERROR, + (errcode_for_file_access(), +#if PG_VERSION_NUM < PG_VERSION_15 + errmsg("unexpected EOF for tape %d: requested %zu bytes, read %zu bytes", + tapenum, t_len - sizeof(uint32), nread))); +#else + errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes", + tape, t_len - sizeof(uint32), nread))); +#endif + + return tuple; +} + +/* + * hashagg_finish_initial_spills + * + * After a HashAggBatch has been processed, it may have spilled tuples to + * disk. If so, turn the spilled partitions into new batches that must later + * be executed. + */ +static void +hashagg_finish_initial_spills(AggState *aggstate) +{ + int setno; + int total_npartitions = 0; + + if (aggstate->hash_spills != NULL) + { + for (setno = 0; setno < aggstate->num_hashes; setno++) + { + HashAggSpill *spill = &aggstate->hash_spills[setno]; + + total_npartitions += spill->npartitions; + hashagg_spill_finish(aggstate, spill, setno); + } + + /* + * We're not processing tuples from outer plan any more; only + * processing batches of spilled tuples. The initial spill structures + * are no longer needed. + */ + pfree(aggstate->hash_spills); + aggstate->hash_spills = NULL; + } + + hash_agg_update_metrics(aggstate, false, total_npartitions); + aggstate->hash_spill_mode = false; +} + +/* + * hashagg_spill_finish + * + * Transform spill partitions into new batches. + */ +static void +hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, int setno) +{ + int i; + int used_bits = 32 - spill->shift; + + if (spill->npartitions == 0) + return; /* didn't spill */ + + for (i = 0; i < spill->npartitions; i++) + { +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeSet *tapeset = aggstate->hash_tapeinfo->tapeset; + int tapenum = spill->partitions[i]; +#else + LogicalTape *tape = spill->partitions[i]; +#endif + HashAggBatch *new_batch; + double cardinality; + + /* if the partition is empty, don't create a new batch of work */ + if (spill->ntuples[i] == 0) + continue; + + cardinality = estimateHyperLogLog(&spill->hll_card[i]); + freeHyperLogLog(&spill->hll_card[i]); + + /* rewinding frees the buffer while not in use */ +#if PG_VERSION_NUM < PG_VERSION_15 + LogicalTapeRewindForRead(tapeset, tapenum, + HASHAGG_READ_BUFFER_SIZE); +#else + LogicalTapeRewindForRead(tape, HASHAGG_READ_BUFFER_SIZE); +#endif + +#if PG_VERSION_NUM < PG_VERSION_15 + new_batch = hashagg_batch_new(tapeset, tapenum, setno, +#else + new_batch = hashagg_batch_new(tape, setno, +#endif + spill->ntuples[i], cardinality, + used_bits); + aggstate->hash_batches = lappend(aggstate->hash_batches, new_batch); + aggstate->hash_batches_used++; + } + + pfree(spill->ntuples); + pfree(spill->hll_card); + pfree(spill->partitions); +} + +/* + * Free resources related to a spilled HashAgg. + */ +static void +hashagg_reset_spill_state(AggState *aggstate) +{ + /* free spills from initial pass */ + if (aggstate->hash_spills != NULL) + { + int setno; + + for (setno = 0; setno < aggstate->num_hashes; setno++) + { + HashAggSpill *spill = &aggstate->hash_spills[setno]; + + pfree(spill->ntuples); + pfree(spill->partitions); + } + pfree(aggstate->hash_spills); + aggstate->hash_spills = NULL; + } + + /* free batches */ + list_free_deep(aggstate->hash_batches); + aggstate->hash_batches = NIL; + + /* close tape set */ +#if PG_VERSION_NUM < PG_VERSION_15 + if (aggstate->hash_tapeinfo != NULL) +#else + if (aggstate->hash_tapeset != NULL) +#endif + { +#if PG_VERSION_NUM < PG_VERSION_15 + HashTapeInfo *tapeinfo = aggstate->hash_tapeinfo; + + LogicalTapeSetClose(tapeinfo->tapeset); + pfree(tapeinfo->freetapes); + pfree(tapeinfo); + aggstate->hash_tapeinfo = NULL; +#else + LogicalTapeSetClose(aggstate->hash_tapeset); + aggstate->hash_tapeset = NULL; +#endif + } +} + + +/* ----------------- + * VExecInitAgg + * + * Creates the run-time information for the agg node produced by the + * planner and initializes its outer subtree. + * + * ----------------- + */ +static AggState * +VExecInitAgg(Agg *node, EState *estate, int eflags) +{ + AggState *aggstate; + AggStatePerAgg peraggs; + AggStatePerTrans pertransstates; + AggStatePerGroup *pergroups; + Plan *outerPlan; + ExprContext *econtext; + TupleDesc scanDesc; + int max_aggno; + int max_transno; + int numaggrefs; + int numaggs; + int numtrans; + int phase; + int phaseidx; + ListCell *l; + Bitmapset *all_grouped_cols = NULL; + int numGroupingSets = 1; + int numPhases; + int numHashes; + int i = 0; + int j = 0; + bool use_hashing = (node->aggstrategy == AGG_HASHED || + node->aggstrategy == AGG_MIXED); + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); + + /* + * create state structure + */ + aggstate = makeNode(AggState); + aggstate->ss.ps.plan = (Plan *) node; + aggstate->ss.ps.state = estate; + aggstate->ss.ps.ExecProcNode = ExecAgg; + + aggstate->aggs = NIL; + aggstate->numaggs = 0; + aggstate->numtrans = 0; + aggstate->aggstrategy = node->aggstrategy; + aggstate->aggsplit = node->aggsplit; + aggstate->maxsets = 0; + aggstate->projected_set = -1; + aggstate->current_set = 0; + aggstate->peragg = NULL; + aggstate->pertrans = NULL; + aggstate->curperagg = NULL; + aggstate->curpertrans = NULL; + aggstate->input_done = false; + aggstate->agg_done = false; + aggstate->pergroups = NULL; + aggstate->grp_firstTuple = NULL; + aggstate->sort_in = NULL; + aggstate->sort_out = NULL; + + /* + * phases[0] always exists, but is dummy in sorted/plain mode + */ + numPhases = (use_hashing ? 1 : 2); + numHashes = (use_hashing ? 1 : 0); + + /* + * Calculate the maximum number of grouping sets in any phase; this + * determines the size of some allocations. Also calculate the number of + * phases, since all hashed/mixed nodes contribute to only a single phase. + */ + if (node->groupingSets) + { + numGroupingSets = list_length(node->groupingSets); + + foreach(l, node->chain) + { + Agg *agg = lfirst(l); + + numGroupingSets = Max(numGroupingSets, + list_length(agg->groupingSets)); + + /* + * additional AGG_HASHED aggs become part of phase 0, but all + * others add an extra phase. + */ + if (agg->aggstrategy != AGG_HASHED) + ++numPhases; + else + ++numHashes; + } + } + + aggstate->maxsets = numGroupingSets; + aggstate->numphases = numPhases; + + aggstate->aggcontexts = (ExprContext **) + palloc0(sizeof(ExprContext *) * numGroupingSets); + + /* + * Create expression contexts. We need three or more, one for + * per-input-tuple processing, one for per-output-tuple processing, one + * for all the hashtables, and one for each grouping set. The per-tuple + * memory context of the per-grouping-set ExprContexts (aggcontexts) + * replaces the standalone memory context formerly used to hold transition + * values. We cheat a little by using ExecAssignExprContext() to build + * all of them. + * + * NOTE: the details of what is stored in aggcontexts and what is stored + * in the regular per-query memory context are driven by a simple + * decision: we want to reset the aggcontext at group boundaries (if not + * hashing) and in ExecReScanAgg to recover no-longer-wanted space. + */ + ExecAssignExprContext(estate, &aggstate->ss.ps); + aggstate->tmpcontext = aggstate->ss.ps.ps_ExprContext; + + for (i = 0; i < numGroupingSets; ++i) + { + ExecAssignExprContext(estate, &aggstate->ss.ps); + aggstate->aggcontexts[i] = aggstate->ss.ps.ps_ExprContext; + } + + if (use_hashing) + aggstate->hashcontext = CreateWorkExprContext(estate); + + ExecAssignExprContext(estate, &aggstate->ss.ps); + + /* + * Initialize child nodes. + * + * If we are doing a hashed aggregation then the child plan does not need + * to handle REWIND efficiently; see ExecReScanAgg. + */ + if (node->aggstrategy == AGG_HASHED) + eflags &= ~EXEC_FLAG_REWIND; + outerPlan = outerPlan(node); + outerPlanState(aggstate) = ExecInitNode(outerPlan, estate, eflags); + + /* + * initialize source tuple type. + */ + aggstate->ss.ps.outerops = + ExecGetResultSlotOps(outerPlanState(&aggstate->ss), + &aggstate->ss.ps.outeropsfixed); + aggstate->ss.ps.outeropsset = true; + + ExecCreateScanSlotFromOuterPlan(estate, &aggstate->ss, + aggstate->ss.ps.outerops); + scanDesc = aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + + /* + * If there are more than two phases (including a potential dummy phase + * 0), input will be resorted using tuplesort. Need a slot for that. + */ + if (numPhases > 2) + { + aggstate->sort_slot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsMinimalTuple); + + /* + * The output of the tuplesort, and the output from the outer child + * might not use the same type of slot. In most cases the child will + * be a Sort, and thus return a TTSOpsMinimalTuple type slot - but the + * input can also be presorted due an index, in which case it could be + * a different type of slot. + * + * XXX: For efficiency it would be good to instead/additionally + * generate expressions with corresponding settings of outerops* for + * the individual phases - deforming is often a bottleneck for + * aggregations with lots of rows per group. If there's multiple + * sorts, we know that all but the first use TTSOpsMinimalTuple (via + * the nodeAgg.c internal tuplesort). + */ + if (aggstate->ss.ps.outeropsfixed && + aggstate->ss.ps.outerops != &TTSOpsMinimalTuple) + aggstate->ss.ps.outeropsfixed = false; + } + + /* + * Initialize result type, slot and projection. + */ + ExecInitResultTupleSlotTL(&aggstate->ss.ps, &TTSOpsVirtual); + ExecAssignProjectionInfo(&aggstate->ss.ps, NULL); + + /* + * initialize child expressions + * + * We expect the parser to have checked that no aggs contain other agg + * calls in their arguments (and just to be sure, we verify it again while + * initializing the plan node). This would make no sense under SQL + * semantics, and it's forbidden by the spec. Because it is true, we + * don't need to worry about evaluating the aggs in any particular order. + * + * Note: execExpr.c finds Aggrefs for us, and adds them to aggstate->aggs. + * Aggrefs in the qual are found here; Aggrefs in the targetlist are found + * during ExecAssignProjectionInfo, above. + */ + aggstate->ss.ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) aggstate); + + /* + * We should now have found all Aggrefs in the targetlist and quals. + */ + numaggrefs = list_length(aggstate->aggs); + max_aggno = -1; + max_transno = -1; + foreach(l, aggstate->aggs) + { + Aggref *aggref = (Aggref *) lfirst(l); + + max_aggno = Max(max_aggno, aggref->aggno); + max_transno = Max(max_transno, aggref->aggtransno); + } + numaggs = max_aggno + 1; + numtrans = max_transno + 1; + + /* + * For each phase, prepare grouping set data and fmgr lookup data for + * compare functions. Accumulate all_grouped_cols in passing. + */ + aggstate->phases = palloc0(numPhases * sizeof(AggStatePerPhaseData)); + + aggstate->num_hashes = numHashes; + if (numHashes) + { + aggstate->perhash = palloc0(sizeof(AggStatePerHashData) * numHashes); + aggstate->phases[0].numsets = 0; + aggstate->phases[0].gset_lengths = palloc(numHashes * sizeof(int)); + aggstate->phases[0].grouped_cols = palloc(numHashes * sizeof(Bitmapset *)); + } + + phase = 0; + for (phaseidx = 0; phaseidx <= list_length(node->chain); ++phaseidx) + { + Agg *aggnode; + Sort *sortnode; + + if (phaseidx > 0) + { + aggnode = list_nth_node(Agg, node->chain, phaseidx - 1); + sortnode = castNode(Sort, aggnode->plan.lefttree); + } + else + { + aggnode = node; + sortnode = NULL; + } + + Assert(phase <= 1 || sortnode); + + if (aggnode->aggstrategy == AGG_HASHED + || aggnode->aggstrategy == AGG_MIXED) + { + AggStatePerPhase phasedata = &aggstate->phases[0]; + AggStatePerHash perhash; + Bitmapset *cols = NULL; + + Assert(phase == 0); + i = phasedata->numsets++; + perhash = &aggstate->perhash[i]; + + /* phase 0 always points to the "real" Agg in the hash case */ + phasedata->aggnode = node; + phasedata->aggstrategy = node->aggstrategy; + + /* but the actual Agg node representing this hash is saved here */ + perhash->aggnode = aggnode; + + phasedata->gset_lengths[i] = perhash->numCols = aggnode->numCols; + + for (j = 0; j < aggnode->numCols; ++j) + cols = bms_add_member(cols, aggnode->grpColIdx[j]); + + phasedata->grouped_cols[i] = cols; + + all_grouped_cols = bms_add_members(all_grouped_cols, cols); + continue; + } + else + { + AggStatePerPhase phasedata = &aggstate->phases[++phase]; + int num_sets; + + phasedata->numsets = num_sets = list_length(aggnode->groupingSets); + + if (num_sets) + { + phasedata->gset_lengths = palloc(num_sets * sizeof(int)); + phasedata->grouped_cols = palloc(num_sets * sizeof(Bitmapset *)); + + i = 0; + foreach(l, aggnode->groupingSets) + { + int current_length = list_length(lfirst(l)); + Bitmapset *cols = NULL; + + /* planner forces this to be correct */ + for (j = 0; j < current_length; ++j) + cols = bms_add_member(cols, aggnode->grpColIdx[j]); + + phasedata->grouped_cols[i] = cols; + phasedata->gset_lengths[i] = current_length; + + ++i; + } + + all_grouped_cols = bms_add_members(all_grouped_cols, + phasedata->grouped_cols[0]); + } + else + { + Assert(phaseidx == 0); + + phasedata->gset_lengths = NULL; + phasedata->grouped_cols = NULL; + } + + /* + * If we are grouping, precompute fmgr lookup data for inner loop. + */ + if (aggnode->aggstrategy == AGG_SORTED) + { + i = 0; + + Assert(aggnode->numCols > 0); + + /* + * Build a separate function for each subset of columns that + * need to be compared. + */ + phasedata->eqfunctions = + (ExprState **) palloc0(aggnode->numCols * sizeof(ExprState *)); + + /* for each grouping set */ + for (i = 0; i < phasedata->numsets; i++) + { + int length = phasedata->gset_lengths[i]; + + /* nothing to do for empty grouping set */ + if (length == 0) + continue; + + /* if we already had one of this length, it'll do */ + if (phasedata->eqfunctions[length - 1] != NULL) + continue; + + phasedata->eqfunctions[length - 1] = + execTuplesMatchPrepare(scanDesc, + length, + aggnode->grpColIdx, + aggnode->grpOperators, + aggnode->grpCollations, + (PlanState *) aggstate); + } + + /* and for all grouped columns, unless already computed */ + if (phasedata->eqfunctions[aggnode->numCols - 1] == NULL) + { + phasedata->eqfunctions[aggnode->numCols - 1] = + execTuplesMatchPrepare(scanDesc, + aggnode->numCols, + aggnode->grpColIdx, + aggnode->grpOperators, + aggnode->grpCollations, + (PlanState *) aggstate); + } + } + + phasedata->aggnode = aggnode; + phasedata->aggstrategy = aggnode->aggstrategy; + phasedata->sortnode = sortnode; + } + } + + /* + * Convert all_grouped_cols to a descending-order list. + */ + i = -1; + while ((i = bms_next_member(all_grouped_cols, i)) >= 0) + aggstate->all_grouped_cols = lcons_int(i, aggstate->all_grouped_cols); + + /* + * Set up aggregate-result storage in the output expr context, and also + * allocate my private per-agg working storage + */ + econtext = aggstate->ss.ps.ps_ExprContext; + econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numaggs); + econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numaggs); + + peraggs = (AggStatePerAgg) palloc0(sizeof(AggStatePerAggData) * numaggs); + pertransstates = (AggStatePerTrans) palloc0(sizeof(AggStatePerTransData) * numtrans); + + aggstate->peragg = peraggs; + aggstate->pertrans = pertransstates; + + + aggstate->all_pergroups = + (AggStatePerGroup *) palloc0(sizeof(AggStatePerGroup) + * (numGroupingSets + numHashes)); + pergroups = aggstate->all_pergroups; + + if (node->aggstrategy != AGG_HASHED) + { + for (i = 0; i < numGroupingSets; i++) + { + pergroups[i] = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData) + * numaggs); + } + + aggstate->pergroups = pergroups; + pergroups += numGroupingSets; + } + + /* + * Hashing can only appear in the initial phase. + */ + if (use_hashing) + { + Plan *outerplan = outerPlan(node); + uint64 totalGroups = 0; + + aggstate->hash_metacxt = AllocSetContextCreate(aggstate->ss.ps.state->es_query_cxt, + "HashAgg meta context", + ALLOCSET_DEFAULT_SIZES); + aggstate->hash_spill_rslot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsMinimalTuple); + aggstate->hash_spill_wslot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsVirtual); + + /* this is an array of pointers, not structures */ + aggstate->hash_pergroup = pergroups; + + aggstate->hashentrysize = hash_agg_entry_size(aggstate->numtrans, + outerplan->plan_width, + node->transitionSpace); + + /* + * Consider all of the grouping sets together when setting the limits + * and estimating the number of partitions. This can be inaccurate + * when there is more than one grouping set, but should still be + * reasonable. + */ + for (i = 0; i < aggstate->num_hashes; i++) + totalGroups += aggstate->perhash[i].aggnode->numGroups; + + hash_agg_set_limits(aggstate->hashentrysize, totalGroups, 0, + &aggstate->hash_mem_limit, + &aggstate->hash_ngroups_limit, + &aggstate->hash_planned_partitions); + find_hash_columns(aggstate); + + /* Skip massive memory allocation if we are just doing EXPLAIN */ + if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) + build_hash_tables(aggstate); + + aggstate->table_filled = false; + + /* Initialize this to 1, meaning nothing spilled, yet */ + aggstate->hash_batches_used = 1; + } + + /* + * Initialize current phase-dependent values to initial phase. The initial + * phase is 1 (first sort pass) for all strategies that use sorting (if + * hashing is being done too, then phase 0 is processed last); but if only + * hashing is being done, then phase 0 is all there is. + */ + if (node->aggstrategy == AGG_HASHED) + { + aggstate->current_phase = 0; + initialize_phase(aggstate, 0); + select_current_set(aggstate, 0, true); + } + else + { + aggstate->current_phase = 1; + initialize_phase(aggstate, 1); + select_current_set(aggstate, 0, false); + } + + /* + * Perform lookups of aggregate function info, and initialize the + * unchanging fields of the per-agg and per-trans data. + */ + foreach(l, aggstate->aggs) + { + Aggref *aggref = lfirst(l); + AggStatePerAgg peragg; + AggStatePerTrans pertrans; +#if PG_VERSION_NUM < PG_VERSION_15 + Oid inputTypes[FUNC_MAX_ARGS]; + int numArguments; +#else + Oid aggTransFnInputTypes[FUNC_MAX_ARGS]; + int numAggTransFnArgs; +#endif + int numDirectArgs; + HeapTuple aggTuple; + Form_pg_aggregate aggform; + AclResult aclresult; + Oid finalfn_oid; + Oid serialfn_oid, + deserialfn_oid; + Oid aggOwner; + Expr *finalfnexpr; + Oid aggtranstype; + + /* Planner should have assigned aggregate to correct level */ + Assert(aggref->agglevelsup == 0); + /* ... and the split mode should match */ + Assert(aggref->aggsplit == aggstate->aggsplit); + + peragg = &peraggs[aggref->aggno]; + + /* Check if we initialized the state for this aggregate already. */ + if (peragg->aggref != NULL) + continue; + + peragg->aggref = aggref; + peragg->transno = aggref->aggtransno; + + /* Fetch the pg_aggregate row */ + aggTuple = SearchSysCache1(AGGFNOID, + ObjectIdGetDatum(aggref->aggfnoid)); + if (!HeapTupleIsValid(aggTuple)) + elog(ERROR, "cache lookup failed for aggregate %u", + aggref->aggfnoid); + aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); + + /* Check permission to call aggregate function */ + aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(), + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_AGGREGATE, + get_func_name(aggref->aggfnoid)); + InvokeFunctionExecuteHook(aggref->aggfnoid); + + /* planner recorded transition state type in the Aggref itself */ + aggtranstype = aggref->aggtranstype; + Assert(OidIsValid(aggtranstype)); + + /* Final function only required if we're finalizing the aggregates */ + if (DO_AGGSPLIT_SKIPFINAL(aggstate->aggsplit)) + peragg->finalfn_oid = finalfn_oid = InvalidOid; + else + peragg->finalfn_oid = finalfn_oid = aggform->aggfinalfn; + + serialfn_oid = InvalidOid; + deserialfn_oid = InvalidOid; + + /* + * Check if serialization/deserialization is required. We only do it + * for aggregates that have transtype INTERNAL. + */ + if (aggtranstype == INTERNALOID) + { + /* + * The planner should only have generated a serialize agg node if + * every aggregate with an INTERNAL state has a serialization + * function. Verify that. + */ + if (DO_AGGSPLIT_SERIALIZE(aggstate->aggsplit)) + { + /* serialization only valid when not running finalfn */ + Assert(DO_AGGSPLIT_SKIPFINAL(aggstate->aggsplit)); + + if (!OidIsValid(aggform->aggserialfn)) + elog(ERROR, "serialfunc not provided for serialization aggregation"); + serialfn_oid = aggform->aggserialfn; + } + + /* Likewise for deserialization functions */ + if (DO_AGGSPLIT_DESERIALIZE(aggstate->aggsplit)) + { + /* deserialization only valid when combining states */ + Assert(DO_AGGSPLIT_COMBINE(aggstate->aggsplit)); + + if (!OidIsValid(aggform->aggdeserialfn)) + elog(ERROR, "deserialfunc not provided for deserialization aggregation"); + deserialfn_oid = aggform->aggdeserialfn; + } + } + + /* Check that aggregate owner has permission to call component fns */ + { + HeapTuple procTuple; + + procTuple = SearchSysCache1(PROCOID, + ObjectIdGetDatum(aggref->aggfnoid)); + if (!HeapTupleIsValid(procTuple)) + elog(ERROR, "cache lookup failed for function %u", + aggref->aggfnoid); + aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner; + ReleaseSysCache(procTuple); + + if (OidIsValid(finalfn_oid)) + { + aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, + get_func_name(finalfn_oid)); + InvokeFunctionExecuteHook(finalfn_oid); + } + if (OidIsValid(serialfn_oid)) + { + aclresult = pg_proc_aclcheck(serialfn_oid, aggOwner, + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, + get_func_name(serialfn_oid)); + InvokeFunctionExecuteHook(serialfn_oid); + } + if (OidIsValid(deserialfn_oid)) + { + aclresult = pg_proc_aclcheck(deserialfn_oid, aggOwner, + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, + get_func_name(deserialfn_oid)); + InvokeFunctionExecuteHook(deserialfn_oid); + } + } + + /* + * Get actual datatypes of the (nominal) aggregate inputs. These + * could be different from the agg's declared input types, when the + * agg accepts ANY or a polymorphic type. + */ +#if PG_VERSION_NUM < PG_VERSION_15 + numArguments = get_aggregate_argtypes(aggref, inputTypes); +#else + numAggTransFnArgs = get_aggregate_argtypes(aggref, + aggTransFnInputTypes); +#endif + + /* Count the "direct" arguments, if any */ + numDirectArgs = list_length(aggref->aggdirectargs); + + /* Detect how many arguments to pass to the finalfn */ + if (aggform->aggfinalextra) +#if PG_VERSION_NUM < PG_VERSION_15 + peragg->numFinalArgs = numArguments + 1; +#else + peragg->numFinalArgs = numAggTransFnArgs + 1; +#endif + else + peragg->numFinalArgs = numDirectArgs + 1; + + /* Initialize any direct-argument expressions */ + peragg->aggdirectargs = ExecInitExprList(aggref->aggdirectargs, + (PlanState *) aggstate); + + /* + * build expression trees using actual argument & result types for the + * finalfn, if it exists and is required. + */ + if (OidIsValid(finalfn_oid)) + { +#if PG_VERSION_NUM < PG_VERSION_15 + build_aggregate_finalfn_expr(inputTypes, +#else + build_aggregate_finalfn_expr(aggTransFnInputTypes, +#endif + peragg->numFinalArgs, + aggtranstype, + aggref->aggtype, + aggref->inputcollid, + finalfn_oid, + &finalfnexpr); + fmgr_info(finalfn_oid, &peragg->finalfn); + fmgr_info_set_expr((Node *) finalfnexpr, &peragg->finalfn); + } + + /* get info about the output value's datatype */ + get_typlenbyval(aggref->aggtype, + &peragg->resulttypeLen, + &peragg->resulttypeByVal); + + /* + * Build working state for invoking the transition function, if we + * haven't done it already. + */ + pertrans = &pertransstates[aggref->aggtransno]; + if (pertrans->aggref == NULL) + { + Datum textInitVal; + Datum initValue; + bool initValueIsNull; + Oid transfn_oid; + + /* + * If this aggregation is performing state combines, then instead + * of using the transition function, we'll use the combine function. + */ + if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit)) + { + transfn_oid = aggform->aggcombinefn; + + /* If not set then the planner messed up */ + if (!OidIsValid(transfn_oid)) + elog(ERROR, "combinefn not set for aggregate function"); + } + else + transfn_oid = aggform->aggtransfn; + +#if PG_VERSION_NUM < PG_VERSION_15 + aclresult = pg_proc_aclcheck(transfn_oid, aggOwner, + ACL_EXECUTE); +#else + aclresult = pg_proc_aclcheck(transfn_oid, aggOwner, ACL_EXECUTE); +#endif + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, + get_func_name(transfn_oid)); + InvokeFunctionExecuteHook(transfn_oid); + + /* + * initval is potentially null, so don't try to access it as a + * struct field. Must do it the hard way with SysCacheGetAttr. + */ + textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple, + Anum_pg_aggregate_agginitval, + &initValueIsNull); + if (initValueIsNull) + initValue = (Datum) 0; + else + initValue = GetAggInitVal(textInitVal, aggtranstype); + +#if PG_VERSION_NUM < PG_VERSION_15 + build_pertrans_for_aggref(pertrans, aggstate, estate, + aggref, transfn_oid, aggtranstype, + serialfn_oid, deserialfn_oid, + initValue, initValueIsNull, + inputTypes, numArguments); +#else + if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit)) + { + Oid combineFnInputTypes[] = {aggtranstype, + aggtranstype}; + + /* + * When combining there's only one input, the to-be-combined + * transition value. The transition value is not counted + * here. + */ + pertrans->numTransInputs = 1; + + /* aggcombinefn always has two arguments of aggtranstype */ + build_pertrans_for_aggref(pertrans, aggstate, estate, + aggref, transfn_oid, aggtranstype, + serialfn_oid, deserialfn_oid, + initValue, initValueIsNull, + combineFnInputTypes, 2); + + /* + * Ensure that a combine function to combine INTERNAL states + * is not strict. This should have been checked during CREATE + * AGGREGATE, but the strict property could have been changed + * since then. + */ + if (pertrans->transfn.fn_strict && aggtranstype == INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("combine function with transition type %s must not be declared STRICT", + format_type_be(aggtranstype)))); + } + else + { + /* Detect how many arguments to pass to the transfn */ + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + pertrans->numTransInputs = list_length(aggref->args); + else + pertrans->numTransInputs = numAggTransFnArgs; + + build_pertrans_for_aggref(pertrans, aggstate, estate, + aggref, transfn_oid, aggtranstype, + serialfn_oid, deserialfn_oid, + initValue, initValueIsNull, + aggTransFnInputTypes, + numAggTransFnArgs); + + /* + * If the transfn is strict and the initval is NULL, make sure + * input type and transtype are the same (or at least + * binary-compatible), so that it's OK to use the first + * aggregated input value as the initial transValue. This + * should have been checked at agg definition time, but we + * must check again in case the transfn's strictness property + * has been changed. + */ + if (pertrans->transfn.fn_strict && pertrans->initValueIsNull) + { + if (numAggTransFnArgs <= numDirectArgs || + !IsBinaryCoercible(aggTransFnInputTypes[numDirectArgs], + aggtranstype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("aggregate %u needs to have compatible input type and transition type", + aggref->aggfnoid))); + } + } +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ + } + else + pertrans->aggshared = true; + ReleaseSysCache(aggTuple); + } + + /* + * Update aggstate->numaggs to be the number of unique aggregates found. + * Also set numstates to the number of unique transition states found. + */ + aggstate->numaggs = numaggs; + aggstate->numtrans = numtrans; + + /* + * Last, check whether any more aggregates got added onto the node while + * we processed the expressions for the aggregate arguments (including not + * only the regular arguments and FILTER expressions handled immediately + * above, but any direct arguments we might've handled earlier). If so, + * we have nested aggregate functions, which is semantically nonsensical, + * so complain. (This should have been caught by the parser, so we don't + * need to work hard on a helpful error message; but we defend against it + * here anyway, just to be sure.) + */ + if (numaggrefs != list_length(aggstate->aggs)) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("aggregate function calls cannot be nested"))); + + /* + * Build expressions doing all the transition work at once. We build a + * different one for each phase, as the number of transition function + * invocation can differ between phases. Note this'll work both for + * transition and combination functions (although there'll only be one + * phase in the latter case). + */ + for (phaseidx = 0; phaseidx < aggstate->numphases; phaseidx++) + { + AggStatePerPhase statePerPhase = &aggstate->phases[phaseidx]; + bool dohash = false; + bool dosort = false; + + /* phase 0 doesn't necessarily exist */ + if (!statePerPhase->aggnode) + continue; + + if (aggstate->aggstrategy == AGG_MIXED && phaseidx == 1) + { + /* + * Phase one, and only phase one, in a mixed agg performs both + * sorting and aggregation. + */ + dohash = true; + dosort = true; + } + else if (aggstate->aggstrategy == AGG_MIXED && phaseidx == 0) + { + /* + * No need to compute a transition function for an AGG_MIXED phase + * 0 - the contents of the hashtables will have been computed + * during phase 1. + */ + continue; + } + else if (statePerPhase->aggstrategy == AGG_PLAIN || + statePerPhase->aggstrategy == AGG_SORTED) + { + dohash = false; + dosort = true; + } + else if (statePerPhase->aggstrategy == AGG_HASHED) + { + dohash = true; + dosort = false; + } + else + Assert(false); + + statePerPhase->evaltrans = ExecBuildAggTrans(aggstate, statePerPhase, dosort, dohash, + false); + + /* cache compiled expression for outer slot without NULL check */ + statePerPhase->evaltrans_cache[0][0] = statePerPhase->evaltrans; + } + + return aggstate; +} + +/* + * Build the state needed to calculate a state value for an aggregate. + * + * This initializes all the fields in 'pertrans'. 'aggref' is the aggregate +#if PG_VERSION_NUM < PG_VERSION_15 + * to initialize the state for. 'aggtransfn', 'aggtranstype', and the rest +#else + * to initialize the state for. 'transfn_oid', 'aggtranstype', and the rest +#endif + * of the arguments could be calculated from 'aggref', but the caller has + * calculated them already, so might as well pass them. +#if PG_VERSION_NUM >= PG_VERSION_15 + * + * 'transfn_oid' may be either the Oid of the aggtransfn or the aggcombinefn. +#endif + */ +static void +build_pertrans_for_aggref(AggStatePerTrans pertrans, + AggState *aggstate, EState *estate, + Aggref *aggref, +#if PG_VERSION_NUM < PG_VERSION_15 + Oid aggtransfn, Oid aggtranstype, +#else + Oid transfn_oid, Oid aggtranstype, +#endif + Oid aggserialfn, Oid aggdeserialfn, + Datum initValue, bool initValueIsNull, + Oid *inputTypes, int numArguments) +{ + int numGroupingSets = Max(aggstate->maxsets, 1); +#if PG_VERSION_NUM >= PG_VERSION_15 + Expr *transfnexpr; + int numTransArgs; +#endif + Expr *serialfnexpr = NULL; + Expr *deserialfnexpr = NULL; + ListCell *lc; + int numInputs; + int numDirectArgs; + List *sortlist; + int numSortCols; + int numDistinctCols; + int i; + + /* Begin filling in the pertrans data */ + pertrans->aggref = aggref; + pertrans->aggshared = false; + pertrans->aggCollation = aggref->inputcollid; +#if PG_VERSION_NUM < PG_VERSION_15 + pertrans->transfn_oid = aggtransfn; +#else + pertrans->transfn_oid = transfn_oid; +#endif + pertrans->serialfn_oid = aggserialfn; + pertrans->deserialfn_oid = aggdeserialfn; + pertrans->initValue = initValue; + pertrans->initValueIsNull = initValueIsNull; + + /* Count the "direct" arguments, if any */ + numDirectArgs = list_length(aggref->aggdirectargs); + + /* Count the number of aggregated input columns */ + pertrans->numInputs = numInputs = list_length(aggref->args); + + pertrans->aggtranstype = aggtranstype; + +#if PG_VERSION_NUM >= PG_VERSION_15 + /* account for the current transition state */ + numTransArgs = pertrans->numTransInputs + 1; +#endif + + /* +#if PG_VERSION_NUM < PG_VERSION_15 + * When combining states, we have no use at all for the aggregate + * function's transfn. Instead we use the combinefn. In this case, the + * transfn and transfn_oid fields of pertrans refer to the combine + * function rather than the transition function. +#else + * Set up infrastructure for calling the transfn. Note that invtrans is + * not needed here. +#endif + */ + +#if PG_VERSION_NUM < PG_VERSION_15 + if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit)) + { + Expr *combinefnexpr; + size_t numTransArgs; + + /* + * When combining there's only one input, the to-be-combined added + * transition value from below (this node's transition value is + * counted separately). + */ + pertrans->numTransInputs = 1; + + /* account for the current transition state */ + numTransArgs = pertrans->numTransInputs + 1; + + build_aggregate_combinefn_expr(aggtranstype, + aggref->inputcollid, + aggtransfn, + &combinefnexpr); + fmgr_info(aggtransfn, &pertrans->transfn); + fmgr_info_set_expr((Node *) combinefnexpr, &pertrans->transfn); + + pertrans->transfn_fcinfo = + (FunctionCallInfo) palloc(SizeForFunctionCallInfo(2)); + InitFunctionCallInfoData(*pertrans->transfn_fcinfo, + &pertrans->transfn, + numTransArgs, + pertrans->aggCollation, + (void *) aggstate, NULL); + + /* + * Ensure that a combine function to combine INTERNAL states is not + * strict. This should have been checked during CREATE AGGREGATE, but + * the strict property could have been changed since then. + */ + if (pertrans->transfn.fn_strict && aggtranstype == INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("combine function with transition type %s must not be declared STRICT", + format_type_be(aggtranstype)))); + } + else + { + Expr *transfnexpr; + size_t numTransArgs; + + /* Detect how many arguments to pass to the transfn */ + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + pertrans->numTransInputs = numInputs; + else + pertrans->numTransInputs = numArguments; + + /* account for the current transition state */ + numTransArgs = pertrans->numTransInputs + 1; +#else + build_aggregate_transfn_expr(inputTypes, + numArguments, + numDirectArgs, + aggref->aggvariadic, + aggtranstype, + aggref->inputcollid, + transfn_oid, + InvalidOid, + &transfnexpr, + NULL); +#endif + +#if PG_VERSION_NUM < PG_VERSION_15 + /* + * Set up infrastructure for calling the transfn. Note that + * invtransfn is not needed here. + */ + build_aggregate_transfn_expr(inputTypes, + numArguments, + numDirectArgs, + aggref->aggvariadic, + aggtranstype, + aggref->inputcollid, + aggtransfn, + InvalidOid, + &transfnexpr, + NULL); + fmgr_info(aggtransfn, &pertrans->transfn); + fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn); + + pertrans->transfn_fcinfo = + (FunctionCallInfo) palloc(SizeForFunctionCallInfo(numTransArgs)); + InitFunctionCallInfoData(*pertrans->transfn_fcinfo, + &pertrans->transfn, + numTransArgs, + pertrans->aggCollation, + (void *) aggstate, NULL); +#else + fmgr_info(transfn_oid, &pertrans->transfn); + fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn); +#endif + +#if PG_VERSION_NUM < PG_VERSION_15 + /* + * If the transfn is strict and the initval is NULL, make sure input + * type and transtype are the same (or at least binary-compatible), so + * that it's OK to use the first aggregated input value as the initial + * transValue. This should have been checked at agg definition time, + * but we must check again in case the transfn's strictness property + * has been changed. + */ + if (pertrans->transfn.fn_strict && pertrans->initValueIsNull) + { + if (numArguments <= numDirectArgs || + !IsBinaryCoercible(inputTypes[numDirectArgs], + aggtranstype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("aggregate %u needs to have compatible input type and transition type", + aggref->aggfnoid))); + } + } +#else + pertrans->transfn_fcinfo = + (FunctionCallInfo) palloc(SizeForFunctionCallInfo(numTransArgs)); + InitFunctionCallInfoData(*pertrans->transfn_fcinfo, + &pertrans->transfn, + numTransArgs, + pertrans->aggCollation, + (void *) aggstate, NULL); +#endif + + /* get info about the state value's datatype */ + get_typlenbyval(aggtranstype, + &pertrans->transtypeLen, + &pertrans->transtypeByVal); + + if (OidIsValid(aggserialfn)) + { + build_aggregate_serialfn_expr(aggserialfn, + &serialfnexpr); + fmgr_info(aggserialfn, &pertrans->serialfn); + fmgr_info_set_expr((Node *) serialfnexpr, &pertrans->serialfn); + + pertrans->serialfn_fcinfo = + (FunctionCallInfo) palloc(SizeForFunctionCallInfo(1)); + InitFunctionCallInfoData(*pertrans->serialfn_fcinfo, + &pertrans->serialfn, + 1, + InvalidOid, + (void *) aggstate, NULL); + } + + if (OidIsValid(aggdeserialfn)) + { + build_aggregate_deserialfn_expr(aggdeserialfn, + &deserialfnexpr); + fmgr_info(aggdeserialfn, &pertrans->deserialfn); + fmgr_info_set_expr((Node *) deserialfnexpr, &pertrans->deserialfn); + + pertrans->deserialfn_fcinfo = + (FunctionCallInfo) palloc(SizeForFunctionCallInfo(2)); + InitFunctionCallInfoData(*pertrans->deserialfn_fcinfo, + &pertrans->deserialfn, + 2, + InvalidOid, + (void *) aggstate, NULL); + } + + /* + * If we're doing either DISTINCT or ORDER BY for a plain agg, then we + * have a list of SortGroupClause nodes; fish out the data in them and + * stick them into arrays. We ignore ORDER BY for an ordered-set agg, + * however; the agg's transfn and finalfn are responsible for that. + * + * Note that by construction, if there is a DISTINCT clause then the ORDER + * BY clause is a prefix of it (see transformDistinctClause). + */ + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + { + sortlist = NIL; + numSortCols = numDistinctCols = 0; + } + else if (aggref->aggdistinct) + { + sortlist = aggref->aggdistinct; + numSortCols = numDistinctCols = list_length(sortlist); + Assert(numSortCols >= list_length(aggref->aggorder)); + } + else + { + sortlist = aggref->aggorder; + numSortCols = list_length(sortlist); + numDistinctCols = 0; + } + + pertrans->numSortCols = numSortCols; + pertrans->numDistinctCols = numDistinctCols; + + /* + * If we have either sorting or filtering to do, create a tupledesc and + * slot corresponding to the aggregated inputs (including sort + * expressions) of the agg. + */ + if (numSortCols > 0 || aggref->aggfilter) + { + pertrans->sortdesc = ExecTypeFromTL(aggref->args); + pertrans->sortslot = + ExecInitExtraTupleSlot(estate, pertrans->sortdesc, + &TTSOpsMinimalTuple); + } + + if (numSortCols > 0) + { + /* + * We don't implement DISTINCT or ORDER BY aggs in the HASHED case + * (yet) + */ + Assert(aggstate->aggstrategy != AGG_HASHED && aggstate->aggstrategy != AGG_MIXED); + +#if PG_VERSION_NUM >= PG_VERSION_15 + /* ORDER BY aggregates are not supported with partial aggregation */ + Assert(!DO_AGGSPLIT_COMBINE(aggstate->aggsplit)); +#endif + + /* If we have only one input, we need its len/byval info. */ + if (numInputs == 1) + { + get_typlenbyval(inputTypes[numDirectArgs], + &pertrans->inputtypeLen, + &pertrans->inputtypeByVal); + } + else if (numDistinctCols > 0) + { + /* we will need an extra slot to store prior values */ + pertrans->uniqslot = + ExecInitExtraTupleSlot(estate, pertrans->sortdesc, + &TTSOpsMinimalTuple); + } + + /* Extract the sort information for use later */ + pertrans->sortColIdx = + (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); + pertrans->sortOperators = + (Oid *) palloc(numSortCols * sizeof(Oid)); + pertrans->sortCollations = + (Oid *) palloc(numSortCols * sizeof(Oid)); + pertrans->sortNullsFirst = + (bool *) palloc(numSortCols * sizeof(bool)); + + i = 0; + foreach(lc, sortlist) + { + SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, aggref->args); + + /* the parser should have made sure of this */ + Assert(OidIsValid(sortcl->sortop)); + + pertrans->sortColIdx[i] = tle->resno; + pertrans->sortOperators[i] = sortcl->sortop; + pertrans->sortCollations[i] = exprCollation((Node *) tle->expr); + pertrans->sortNullsFirst[i] = sortcl->nulls_first; + i++; + } + Assert(i == numSortCols); + } + + if (aggref->aggdistinct) + { + Oid *ops; + + Assert(numArguments > 0); + Assert(list_length(aggref->aggdistinct) == numDistinctCols); + + ops = palloc(numDistinctCols * sizeof(Oid)); + + i = 0; + foreach(lc, aggref->aggdistinct) + ops[i++] = ((SortGroupClause *) lfirst(lc))->eqop; + + /* lookup / build the necessary comparators */ + if (numDistinctCols == 1) + fmgr_info(get_opcode(ops[0]), &pertrans->equalfnOne); + else + pertrans->equalfnMulti = + execTuplesMatchPrepare(pertrans->sortdesc, + numDistinctCols, + pertrans->sortColIdx, + ops, + pertrans->sortCollations, + &aggstate->ss.ps); + pfree(ops); + } + + pertrans->sortstates = (Tuplesortstate **) + palloc0(sizeof(Tuplesortstate *) * numGroupingSets); +} + + +static Datum +GetAggInitVal(Datum textInitVal, Oid transtype) +{ + Oid typinput, + typioparam; + char *strInitVal; + Datum initVal; + + getTypeInputInfo(transtype, &typinput, &typioparam); + strInitVal = TextDatumGetCString(textInitVal); + initVal = OidInputFunctionCall(typinput, strInitVal, + typioparam, -1); + pfree(strInitVal); + return initVal; +} + +void +ExecEndAgg(AggState *node) +{ + PlanState *outerPlan; + int transno; + int numGroupingSets = Max(node->maxsets, 1); + int setno; + + /* + * When ending a parallel worker, copy the statistics gathered by the + * worker back into shared memory so that it can be picked up by the main + * process to report in EXPLAIN ANALYZE. + */ + if (node->shared_info && IsParallelWorker()) + { + AggregateInstrumentation *si; + + Assert(ParallelWorkerNumber <= node->shared_info->num_workers); + si = &node->shared_info->sinstrument[ParallelWorkerNumber]; + si->hash_batches_used = node->hash_batches_used; + si->hash_disk_used = node->hash_disk_used; + si->hash_mem_peak = node->hash_mem_peak; + } + + /* Make sure we have closed any open tuplesorts */ + + if (node->sort_in) + tuplesort_end(node->sort_in); + if (node->sort_out) + tuplesort_end(node->sort_out); + + hashagg_reset_spill_state(node); + + if (node->hash_metacxt != NULL) + { + MemoryContextDelete(node->hash_metacxt); + node->hash_metacxt = NULL; + } + + for (transno = 0; transno < node->numtrans; transno++) + { + AggStatePerTrans pertrans = &node->pertrans[transno]; + + for (setno = 0; setno < numGroupingSets; setno++) + { + if (pertrans->sortstates[setno]) + tuplesort_end(pertrans->sortstates[setno]); + } + } + + /* And ensure any agg shutdown callbacks have been called */ + for (setno = 0; setno < numGroupingSets; setno++) + ReScanExprContext(node->aggcontexts[setno]); + if (node->hashcontext) + ReScanExprContext(node->hashcontext); + + /* + * We don't actually free any ExprContexts here (see comment in + * ExecFreeExprContext), just unlinking the output one from the plan node + * suffices. + */ + ExecFreeExprContext(&node->ss.ps); + + /* clean up tuple table */ + ExecClearTuple(node->ss.ss_ScanTupleSlot); + + outerPlan = outerPlanState(node); + ExecEndNode(outerPlan); +} + +void +ExecReScanAgg(AggState *node) +{ + ExprContext *econtext = node->ss.ps.ps_ExprContext; + PlanState *outerPlan = outerPlanState(node); + Agg *aggnode = (Agg *) node->ss.ps.plan; + int transno; + int numGroupingSets = Max(node->maxsets, 1); + int setno; + + node->agg_done = false; + + if (node->aggstrategy == AGG_HASHED) + { + /* + * In the hashed case, if we haven't yet built the hash table then we + * can just return; nothing done yet, so nothing to undo. If subnode's + * chgParam is not NULL then it will be re-scanned by ExecProcNode, + * else no reason to re-scan it at all. + */ + if (!node->table_filled) + return; + + /* + * If we do have the hash table, and it never spilled, and the subplan + * does not have any parameter changes, and none of our own parameter + * changes affect input expressions of the aggregated functions, then + * we can just rescan the existing hash table; no need to build it + * again. + */ + if (outerPlan->chgParam == NULL && !node->hash_ever_spilled && + !bms_overlap(node->ss.ps.chgParam, aggnode->aggParams)) + { + ResetTupleHashIterator(node->perhash[0].hashtable, + &node->perhash[0].hashiter); + select_current_set(node, 0, true); + return; + } + } + + /* Make sure we have closed any open tuplesorts */ + for (transno = 0; transno < node->numtrans; transno++) + { + for (setno = 0; setno < numGroupingSets; setno++) + { + AggStatePerTrans pertrans = &node->pertrans[transno]; + + if (pertrans->sortstates[setno]) + { + tuplesort_end(pertrans->sortstates[setno]); + pertrans->sortstates[setno] = NULL; + } + } + } + + /* + * We don't need to ReScanExprContext the output tuple context here; + * ExecReScan already did it. But we do need to reset our per-grouping-set + * contexts, which may have transvalues stored in them. (We use rescan + * rather than just reset because transfns may have registered callbacks + * that need to be run now.) For the AGG_HASHED case, see below. + */ + + for (setno = 0; setno < numGroupingSets; setno++) + { + ReScanExprContext(node->aggcontexts[setno]); + } + + /* Release first tuple of group, if we have made a copy */ + if (node->grp_firstTuple != NULL) + { + heap_freetuple(node->grp_firstTuple); + node->grp_firstTuple = NULL; + } + ExecClearTuple(node->ss.ss_ScanTupleSlot); + + /* Forget current agg values */ + MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numaggs); + MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numaggs); + + /* + * With AGG_HASHED/MIXED, the hash table is allocated in a sub-context of + * the hashcontext. This used to be an issue, but now, resetting a context + * automatically deletes sub-contexts too. + */ + if (node->aggstrategy == AGG_HASHED || node->aggstrategy == AGG_MIXED) + { + hashagg_reset_spill_state(node); + + node->hash_ever_spilled = false; + node->hash_spill_mode = false; + node->hash_ngroups_current = 0; + + ReScanExprContext(node->hashcontext); + /* Rebuild an empty hash table */ + build_hash_tables(node); + node->table_filled = false; + /* iterator will be reset when the table is filled */ + + hashagg_recompile_expressions(node, false, false); + } + + if (node->aggstrategy != AGG_HASHED) + { + /* + * Reset the per-group state (in particular, mark transvalues null) + */ + for (setno = 0; setno < numGroupingSets; setno++) + { + MemSet(node->pergroups[setno], 0, + sizeof(AggStatePerGroupData) * node->numaggs); + } + + /* reset to phase 1 */ + initialize_phase(node, 1); + + node->input_done = false; + node->projected_set = -1; + } + + if (outerPlan->chgParam == NULL) + ExecReScan(outerPlan); +} + + +/*********************************************************************** + * API exposed to aggregate functions + ***********************************************************************/ + + +/* + * AggCheckCallContext - test if a SQL function is being called as an aggregate + * + * The transition and/or final functions of an aggregate may want to verify + * that they are being called as aggregates, rather than as plain SQL + * functions. They should use this function to do so. The return value + * is nonzero if being called as an aggregate, or zero if not. (Specific + * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more + * values could conceivably appear in future.) + * + * If aggcontext isn't NULL, the function also stores at *aggcontext the + * identity of the memory context that aggregate transition values are being + * stored in. Note that the same aggregate call site (flinfo) may be called + * interleaved on different transition values in different contexts, so it's + * not kosher to cache aggcontext under fn_extra. It is, however, kosher to + * cache it in the transvalue itself (for internal-type transvalues). + */ +int +AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + if (aggcontext) + { + AggState *aggstate = ((AggState *) fcinfo->context); + ExprContext *cxt = aggstate->curaggcontext; + + *aggcontext = cxt->ecxt_per_tuple_memory; + } + return AGG_CONTEXT_AGGREGATE; + } + if (fcinfo->context && IsA(fcinfo->context, WindowAggState)) + { + if (aggcontext) + *aggcontext = ((WindowAggState *) fcinfo->context)->curaggcontext; + return AGG_CONTEXT_WINDOW; + } + + /* this is just to prevent "uninitialized variable" warnings */ + if (aggcontext) + *aggcontext = NULL; + return 0; +} + +/* + * AggGetAggref - allow an aggregate support function to get its Aggref + * + * If the function is being called as an aggregate support function, + * return the Aggref node for the aggregate call. Otherwise, return NULL. + * + * Aggregates sharing the same inputs and transition functions can get + * merged into a single transition calculation. If the transition function + * calls AggGetAggref, it will get some one of the Aggrefs for which it is + * executing. It must therefore not pay attention to the Aggref fields that + * relate to the final function, as those are indeterminate. But if a final + * function calls AggGetAggref, it will get a precise result. + * + * Note that if an aggregate is being used as a window function, this will + * return NULL. We could provide a similar function to return the relevant + * WindowFunc node in such cases, but it's not needed yet. + */ +Aggref * +AggGetAggref(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + AggStatePerAgg curperagg; + AggStatePerTrans curpertrans; + + /* check curperagg (valid when in a final function) */ + curperagg = aggstate->curperagg; + + if (curperagg) + return curperagg->aggref; + + /* check curpertrans (valid when in a transition function) */ + curpertrans = aggstate->curpertrans; + + if (curpertrans) + return curpertrans->aggref; + } + return NULL; +} + +#if PG_VERSION_NUM < PG_VERSION_15 +/* + + * AggGetTempMemoryContext - fetch short-term memory context for aggregates + * + * This is useful in agg final functions; the context returned is one that + * the final function can safely reset as desired. This isn't useful for + * transition functions, since the context returned MAY (we don't promise) + * be the same as the context those are called in. + * + * As above, this is currently not useful for aggs called as window functions. + */ +MemoryContext +AggGetTempMemoryContext(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + + return aggstate->tmpcontext->ecxt_per_tuple_memory; + } + return NULL; +} +#endif + +/* + * AggStateIsShared - find out whether transition state is shared + * + * If the function is being called as an aggregate support function, + * return true if the aggregate's transition state is shared across + * multiple aggregates, false if it is not. + * + * Returns true if not called as an aggregate support function. + * This is intended as a conservative answer, ie "no you'd better not + * scribble on your input". In particular, will return true if the + * aggregate is being used as a window function, which is a scenario + * in which changing the transition state is a bad idea. We might + * want to refine the behavior for the window case in future. + */ +bool +AggStateIsShared(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + AggStatePerAgg curperagg; + AggStatePerTrans curpertrans; + + /* check curperagg (valid when in a final function) */ + curperagg = aggstate->curperagg; + + if (curperagg) + return aggstate->pertrans[curperagg->transno].aggshared; + + /* check curpertrans (valid when in a transition function) */ + curpertrans = aggstate->curpertrans; + + if (curpertrans) + return curpertrans->aggshared; + } + return true; +} + +#if PG_VERSION_NUM < PG_VERSION_15 +/* + * AggRegisterCallback - register a cleanup callback for an aggregate + * + * This is useful for aggs to register shutdown callbacks, which will ensure + * that non-memory resources are freed. The callback will occur just before + * the associated aggcontext (as returned by AggCheckCallContext) is reset, + * either between groups or as a result of rescanning the query. The callback + * will NOT be called on error paths. The typical use-case is for freeing of + * tuplestores or tuplesorts maintained in aggcontext, or pins held by slots + * created by the agg functions. (The callback will not be called until after + * the result of the finalfn is no longer needed, so it's safe for the finalfn + * to return data that will be freed by the callback.) + * + * As above, this is currently not useful for aggs called as window functions. + */ +void +AggRegisterCallback(FunctionCallInfo fcinfo, + ExprContextCallbackFunction func, + Datum arg) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + ExprContext *cxt = aggstate->curaggcontext; + + RegisterExprContextCallback(cxt, func, arg); + + return; + } + elog(ERROR, "aggregate function cannot register a callback in this context"); +} +#endif + +/* ---------------------------------------------------------------- + * Parallel Query Support + * ---------------------------------------------------------------- + */ + +/* Parallel Execution */ + +static Size +ColumnarAggNode_EstimateDSM(CustomScanState *node, + ParallelContext *pcxt) +{ + Size nbytes = 0; + return nbytes; +} + + +static void +ColumnarAggNode_InitializeDSM(CustomScanState *node, + ParallelContext *pcxt, + void *coordinate) +{ +} + + +static void +ColumnarAggNode_ReinitializeDSM(CustomScanState *node, + ParallelContext *pcxt, + void *coordinate) +{ +} + + +static void +ColumnarAggNode_InitializeWorker(CustomScanState *node, + shm_toc *toc, + void *coordinate) +{ +} + +/* CustomScan Aggregator Node */ +#include "nodes/extensible.h" + + +/* CustomScanMethods */ +static Node *CreateVectorAggState(CustomScan *custom_plan); + +/* CustomScanExecMethods */ +static void BeginVectorAgg(CustomScanState *node, EState *estate, int eflags); +static TupleTableSlot *ExecVectorAgg(CustomScanState *node); +static void EndVectorAgg(CustomScanState *node); +static void ExplainAggNode(CustomScanState *node, List *ancestors, ExplainState *es); + +static CustomScanMethods VectorAggNodeMethods = { + "VectorAggNode", /* CustomName */ + CreateVectorAggState, /* CreateCustomScanState */ +}; + +static CustomExecMethods VectorAggNodeExecMethods = { + .CustomName = "VectorAggNode", + + .BeginCustomScan = BeginVectorAgg, + .ExecCustomScan = ExecVectorAgg, + .EndCustomScan = EndVectorAgg, + + .ExplainCustomScan = ExplainAggNode, + + .EstimateDSMCustomScan = ColumnarAggNode_EstimateDSM, + .InitializeDSMCustomScan = ColumnarAggNode_InitializeDSM, + .ReInitializeDSMCustomScan = ColumnarAggNode_ReinitializeDSM, + .InitializeWorkerCustomScan = ColumnarAggNode_InitializeWorker +}; + + +static Node * +CreateVectorAggState(CustomScan *custom_plan) +{ + VectorAggState *vas = (VectorAggState *) newNode( + sizeof(VectorAggState), T_CustomScanState); + + CustomScanState *cscanstate = &vas->css; + cscanstate->methods = &VectorAggNodeExecMethods; + + return (Node *) cscanstate; +} + + +static void +BeginVectorAgg(CustomScanState *css, EState *estate, int eflags) +{ + VectorAggState *vas = (VectorAggState*) css; + CustomScan *cscan = (CustomScan *)css->ss.ps.plan; + Agg *aggNode = (Agg *)linitial(cscan->custom_plans); + + /* Free the exprcontext */ + ExecFreeExprContext(&css->ss.ps); + + /* Clean out the tuple table */ + ExecClearTuple(css->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(css->ss.ss_ScanTupleSlot); + + + vas->aggstate = VExecInitAgg(aggNode, estate, eflags); + + // HYDRA: add leftree to custom agg + outerPlanState(vas) = outerPlanState(vas->aggstate); + + /*Initialize result type and projection. */ + ExecInitResultTypeTL(&vas->css.ss.ps); + + vas->css.ss.ps.ps_ResultTupleSlot = vas->aggstate->ss.ps.ps_ResultTupleSlot; +} + + +static TupleTableSlot * +ExecVectorAgg(CustomScanState *node) +{ + return ExecAgg((PlanState *)node); +} + + +static void +EndVectorAgg(CustomScanState *node) +{ + ExecEndAgg(((VectorAggState *)node)->aggstate); +} + + +static void +ExplainAggNode(CustomScanState *node, List *ancestors, ExplainState *es) +{ + +} + + +CustomScan * +columnar_create_aggregator_node(void) +{ + CustomScan *cscan = (CustomScan *) makeNode(CustomScan); + cscan->methods = &VectorAggNodeMethods; + return cscan; +} + + +void +columnar_register_aggregator_node(void) +{ + RegisterCustomScanMethods(&VectorAggNodeMethods); +} + +#endif diff --git a/columnar/src/backend/columnar/vectorization/types/aggregates.c b/columnar/src/backend/columnar/vectorization/types/aggregates.c new file mode 100644 index 00000000..f0906e1b --- /dev/null +++ b/columnar/src/backend/columnar/vectorization/types/aggregates.c @@ -0,0 +1,457 @@ + +#include "postgres.h" + +#include "fmgr.h" +#include "nodes/execnodes.h" +#include "utils/date.h" +#include "utils/array.h" +#include "utils/numeric.h" +#include "utils/fmgrprotos.h" + +#include "pg_version_constants.h" +#include "columnar/vectorization/types/types.h" +#include "columnar/vectorization/types/numeric.h" + +/* count */ + +PG_FUNCTION_INFO_V1(vemptycount); +Datum vemptycount(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + PG_RETURN_INT64(arg); +} + +PG_FUNCTION_INFO_V1(vanycount); +Datum +vanycount(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + int64 result = arg; + VectorColumn *arg1 = (VectorColumn *) PG_GETARG_POINTER(1); + int i; + + for (i = 0; i < arg1->dimension; i++) + { + if (arg1->isnull[i]) + continue; + + result++; + } + + PG_RETURN_INT64(result); +} + +/* int2 */ + +PG_FUNCTION_INFO_V1(vint2sum); +Datum +vint2sum(PG_FUNCTION_ARGS) +{ + int64 sumX = PG_GETARG_INT64(0); + VectorColumn *arg1 = (VectorColumn*) PG_GETARG_POINTER(1); + int i; + + int16 *vectorValue = (int16*) arg1->value; + + for (i = 0; i < arg1->dimension; i++) + { + if (!arg1->isnull[i]) + sumX += (int64) vectorValue[i]; + } + + PG_RETURN_INT64(sumX); +} + +PG_FUNCTION_INFO_V1(vint2acc); +Datum +vint2acc(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + VectorColumn *arg1 = (VectorColumn*) PG_GETARG_POINTER(1); + Int64AggState *transdata; + int i; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + transdata = (Int64AggState *) ARR_DATA_PTR(transarray); + + int16 *vectorValue = (int16*) arg1->value; + + for (i = 0; i < arg1->dimension; i++) + { + if (!arg1->isnull[i]) + { + transdata->N++; + transdata->sumX += (int64) vectorValue[i]; + } + } + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +PG_FUNCTION_INFO_V1(vint2larger); +Datum vint2larger(PG_FUNCTION_ARGS) +{ + int16 maxValue = PG_GETARG_INT16(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int16 result = maxValue; + int i = 0; + + int16 *vectorValue = (int16*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Max(result, vectorValue[i]); + } + + maxValue = Max(maxValue, result); + + PG_RETURN_INT16(maxValue); +} + +PG_FUNCTION_INFO_V1(vint2smaller); +Datum vint2smaller(PG_FUNCTION_ARGS) +{ + int16 minValue = PG_GETARG_INT32(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int16 result = minValue; + int i = 0; + + int16 *vectorValue = (int16*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Min(result, vectorValue[i]); + } + + minValue = Min(minValue, result); + + PG_RETURN_INT16(minValue); +} + +/* int4 */ + +PG_FUNCTION_INFO_V1(vint4sum); +Datum +vint4sum(PG_FUNCTION_ARGS) +{ + int64 sumX = PG_GETARG_INT64(0); + VectorColumn *arg1 = (VectorColumn*) PG_GETARG_POINTER(1); + int i; + + int32 *vectorValue = (int32*) arg1->value; + + for (i = 0; i < arg1->dimension; i++) + { + if (!arg1->isnull[i]) + sumX += (int64) vectorValue[i]; + } + + PG_RETURN_INT64(sumX); +} + +PG_FUNCTION_INFO_V1(vint4acc); +Datum +vint4acc(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + VectorColumn *arg1 = (VectorColumn*) PG_GETARG_POINTER(1); + Int64AggState *transdata; + int i; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + transdata = (Int64AggState *) ARR_DATA_PTR(transarray); + + int32 *vectorValue = (int32*) arg1->value; + + for (i = 0; i < arg1->dimension; i++) + { + if (!arg1->isnull[i]) + { + transdata->N++; + transdata->sumX += (int64) vectorValue[i]; + } + } + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +PG_FUNCTION_INFO_V1(vint4larger); +Datum vint4larger(PG_FUNCTION_ARGS) +{ + int32 maxValue = PG_GETARG_INT32(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int32 result = maxValue; + int i = 0; + + int32 *vectorValue = (int32*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Max(result, vectorValue[i]); + } + + maxValue = Max(maxValue, result); + + PG_RETURN_INT32(maxValue); +} + +PG_FUNCTION_INFO_V1(vint4smaller); +Datum vint4smaller(PG_FUNCTION_ARGS) +{ + int32 minValue = PG_GETARG_INT32(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int32 result = minValue; + int i = 0; + + int32 *vectorValue = (int32*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Min(result, vectorValue[i]); + } + + minValue = Min(minValue, result); + + PG_RETURN_INT32(minValue); +} + +/* int2 / int4 */ + +PG_FUNCTION_INFO_V1(vint2int4avg); +Datum +vint2int4avg(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Int64AggState *transdata; + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int64AggState)) + elog(ERROR, "expected 2-element int8 array"); + + transdata = (Int64AggState *) ARR_DATA_PTR(transarray); + + /* SQL defines AVG of no values to be NULL */ + if (transdata->N == 0) + PG_RETURN_NULL(); + +#if PG_VERSION_NUM >= PG_VERSION_14 + Numeric sumNumeric, nNumeric, res; + nNumeric = int64_to_numeric(transdata->N); + sumNumeric = int64_to_numeric(transdata->sumX); + res = numeric_div_opt_error(sumNumeric, nNumeric, NULL); + PG_RETURN_NUMERIC(res); +#else + Datum countd, sumd; + countd = DirectFunctionCall1(int8_numeric, + Int64GetDatumFast(transdata->N)); + sumd = DirectFunctionCall1(int8_numeric, + Int64GetDatumFast(transdata->sumX)); + PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd)); +#endif + +} + + +/* int8 */ + +PG_FUNCTION_INFO_V1(vint8acc); +Datum +vint8acc(PG_FUNCTION_ARGS) +{ + Int128AggState *state; + int i; + + state = PG_ARGISNULL(0) ? NULL : (Int128AggState *) PG_GETARG_POINTER(0); + VectorColumn *arg1 = (VectorColumn*) PG_GETARG_POINTER(1); + + MemoryContext aggContext; + MemoryContext oldContext; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + oldContext = MemoryContextSwitchTo(aggContext); + + /* Create the state data on the first call */ + if (state == NULL) + { + state = palloc0(sizeof(Int128AggState)); + state->calcSumX2 = false; + } + + int64 *vectorValue = (int64*) arg1->value; + + for (i = 0; i < arg1->dimension; i++) + { + if (!arg1->isnull[i]) + { + state->N++; + state->sumX += (int128) vectorValue[i]; + } + } + + MemoryContextSwitchTo(oldContext); + + PG_RETURN_NUMERIC(state); +} + +PG_FUNCTION_INFO_V1(vint8sum); +Datum +vint8sum(PG_FUNCTION_ARGS) +{ + Int128AggState *state; + + state = PG_ARGISNULL(0) ? NULL : (Int128AggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || state->N == 0) + PG_RETURN_NULL(); + + Numeric res = int128_to_numeric(state->sumX); + + PG_RETURN_NUMERIC(res); +} + +PG_FUNCTION_INFO_V1(vint8avg); +Datum +vint8avg(PG_FUNCTION_ARGS) +{ + Int128AggState *state; + Numeric sumNumeric, nNumeric, res; + + state = PG_ARGISNULL(0) ? NULL : (Int128AggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || state->N == 0) + PG_RETURN_NULL(); + + sumNumeric = int128_to_numeric(state->sumX); + nNumeric = int128_to_numeric(state->N); + res = numeric_div_opt_error(sumNumeric, nNumeric, NULL); + + PG_RETURN_NUMERIC(res); +} + +PG_FUNCTION_INFO_V1(vint8larger); +Datum vint8larger(PG_FUNCTION_ARGS) +{ + int64 maxValue = PG_GETARG_INT64(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int64 result = 0; + int i = 0; + + int64 *vectorValue = (int64*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Max(maxValue, vectorValue[i]); + } + + maxValue = Max(maxValue, result); + + PG_RETURN_INT64(maxValue); +} + +PG_FUNCTION_INFO_V1(vint8smaller); +Datum vint8smaller(PG_FUNCTION_ARGS) +{ + int64 minValue = PG_GETARG_INT64(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int64 result = minValue; + int i = 0; + + int64 *vectorValue = (int64*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Min(result, vectorValue[i]); + } + + minValue = Min(minValue, result); + + PG_RETURN_INT64(minValue); +} + +// date + +PG_FUNCTION_INFO_V1(vdatelarger); +Datum vdatelarger(PG_FUNCTION_ARGS) +{ + int32 maxValue = PG_GETARG_INT32(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int32 result = maxValue; + int i = 0; + + DateADT *vectorValue = (DateADT*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Max(result, vectorValue[i]); + } + + maxValue = Max(maxValue, result); + + PG_RETURN_INT32(maxValue); +} + +PG_FUNCTION_INFO_V1(vdatesmaller); +Datum vdatesmaller(PG_FUNCTION_ARGS) +{ + int32 minValue = PG_GETARG_INT32(0); + VectorColumn *arg2 = (VectorColumn*) PG_GETARG_POINTER(1); + int32 result = minValue; + int i = 0; + + DateADT *vectorValue = (DateADT*) arg2->value; + + for (i = 0; i < arg2->dimension; i++) + { + if (arg2->isnull[i]) + continue; + + result = Min(result, vectorValue[i]); + } + + minValue = Min(minValue, result); + + PG_RETURN_INT32(minValue); +} \ No newline at end of file diff --git a/columnar/src/backend/columnar/vectorization/types/int.c b/columnar/src/backend/columnar/vectorization/types/int.c index deb441b7..28b1e317 100644 --- a/columnar/src/backend/columnar/vectorization/types/int.c +++ b/columnar/src/backend/columnar/vectorization/types/int.c @@ -1,5 +1,6 @@ #include "postgres.h" +#include "common/int.h" #include "fmgr.h" #include "nodes/execnodes.h" @@ -22,4 +23,4 @@ BUILD_CMP_OPERATOR_INT(int48, int32, int64) // int8 BUILD_CMP_OPERATOR_INT( int8, int64, int64) BUILD_CMP_OPERATOR_INT(int82, int64, int16) -BUILD_CMP_OPERATOR_INT(int84, int64, int32) \ No newline at end of file +BUILD_CMP_OPERATOR_INT(int84, int64, int32) diff --git a/columnar/src/backend/columnar/vectorization/types/numeric.c b/columnar/src/backend/columnar/vectorization/types/numeric.c new file mode 100644 index 00000000..09aad25c --- /dev/null +++ b/columnar/src/backend/columnar/vectorization/types/numeric.c @@ -0,0 +1,451 @@ +/*------------------------------------------------------------------------- + * + * numeric.c + * Numeric PostgreSQL type + * + * $Id* + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/numeric.h" +#include "common/int.h" + +#include "columnar/vectorization/types/numeric.h" +#include "columnar/vectorization/types/types.h" + +#define NBASE 10000 +#define HALF_NBASE 5000 +#define DEC_DIGITS 4 /* decimal digits per NBASE digit */ +#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */ +#define DIV_GUARD_DIGITS 4 + +typedef int16 NumericDigit; + +/* + * The Numeric type as stored on disk. + * + * If the high bits of the first word of a NumericChoice (n_header, or + * n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the + * numeric follows the NumericShort format; if they are NUMERIC_POS or + * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_SPECIAL, + * the value is a NaN or Infinity. We currently always store SPECIAL values + * using just two bytes (i.e. only n_header), but previous releases used only + * the NumericLong format, so we might find 4-byte NaNs (though not infinities) + * on disk if a database has been migrated using pg_upgrade. In either case, + * the low-order bits of a special value's header are reserved and currently + * should always be set to zero. + * + * In the NumericShort format, the remaining 14 bits of the header word + * (n_short.n_header) are allocated as follows: 1 for sign (positive or + * negative), 6 for dynamic scale, and 7 for weight. In practice, most + * commonly-encountered values can be represented this way. + * + * In the NumericLong format, the remaining 14 bits of the header word + * (n_long.n_sign_dscale) represent the display scale; and the weight is + * stored separately in n_weight. + * + * NOTE: by convention, values in the packed form have been stripped of + * all leading and trailing zero digits (where a "digit" is of base NBASE). + * In particular, if the value is zero, there will be no digits at all! + * The weight is arbitrary in that case, but we normally set it to zero. + */ + +struct NumericShort +{ + uint16 n_header; /* Sign + display scale + weight */ + NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */ +}; + +struct NumericLong +{ + uint16 n_sign_dscale; /* Sign + display scale */ + int16 n_weight; /* Weight of 1st digit */ + NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */ +}; + +union NumericChoice +{ + uint16 n_header; /* Header word */ + struct NumericLong n_long; /* Long form (4-byte header) */ + struct NumericShort n_short; /* Short form (2-byte header) */ +}; + +struct NumericData +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + union NumericChoice choice; /* choice of format */ +}; + +/* + * Interpretation of high bits. + */ + +#define NUMERIC_SIGN_MASK 0xC000 +#define NUMERIC_POS 0x0000 +#define NUMERIC_NEG 0x4000 +#define NUMERIC_SHORT 0x8000 +#define NUMERIC_SPECIAL 0xC000 + +#define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK) +#define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT) +#define NUMERIC_IS_SPECIAL(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SPECIAL) + +#define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16)) +#define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16)) + +/* + * If the flag bits are NUMERIC_SHORT or NUMERIC_SPECIAL, we want the short + * header; otherwise, we want the long one. Instead of testing against each + * value, we can just look at the high bit, for a slight efficiency gain. + */ +#define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0) +#define NUMERIC_HEADER_SIZE(n) \ + (VARHDRSZ + sizeof(uint16) + \ + (NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16))) + +/* + * Definitions for special values (NaN, positive infinity, negative infinity). + * + * The two bits after the NUMERIC_SPECIAL bits are 00 for NaN, 01 for positive + * infinity, 11 for negative infinity. (This makes the sign bit match where + * it is in a short-format value, though we make no use of that at present.) + * We could mask off the remaining bits before testing the active bits, but + * currently those bits must be zeroes, so masking would just add cycles. + */ +#define NUMERIC_EXT_SIGN_MASK 0xF000 /* high bits plus NaN/Inf flag bits */ +#define NUMERIC_NAN 0xC000 +#define NUMERIC_PINF 0xD000 +#define NUMERIC_NINF 0xF000 +#define NUMERIC_INF_SIGN_MASK 0x2000 + +#define NUMERIC_EXT_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_EXT_SIGN_MASK) +#define NUMERIC_IS_NAN(n) ((n)->choice.n_header == NUMERIC_NAN) +#define NUMERIC_IS_PINF(n) ((n)->choice.n_header == NUMERIC_PINF) +#define NUMERIC_IS_NINF(n) ((n)->choice.n_header == NUMERIC_NINF) +#define NUMERIC_IS_INF(n) \ + (((n)->choice.n_header & ~NUMERIC_INF_SIGN_MASK) == NUMERIC_PINF) + +/* + * Short format definitions. + */ + +#define NUMERIC_SHORT_SIGN_MASK 0x2000 +#define NUMERIC_SHORT_DSCALE_MASK 0x1F80 +#define NUMERIC_SHORT_DSCALE_SHIFT 7 +#define NUMERIC_SHORT_DSCALE_MAX \ + (NUMERIC_SHORT_DSCALE_MASK >> NUMERIC_SHORT_DSCALE_SHIFT) +#define NUMERIC_SHORT_WEIGHT_SIGN_MASK 0x0040 +#define NUMERIC_SHORT_WEIGHT_MASK 0x003F +#define NUMERIC_SHORT_WEIGHT_MAX NUMERIC_SHORT_WEIGHT_MASK +#define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1)) + +/* + * Extract sign, display scale, weight. These macros extract field values + * suitable for the NumericVar format from the Numeric (on-disk) format. + * + * Note that we don't trouble to ensure that dscale and weight read as zero + * for an infinity; however, that doesn't matter since we never convert + * "special" numerics to NumericVar form. Only the constants defined below + * (const_nan, etc) ever represent a non-finite value as a NumericVar. + */ + +#define NUMERIC_DSCALE_MASK 0x3FFF +#define NUMERIC_DSCALE_MAX NUMERIC_DSCALE_MASK + +#define NUMERIC_SIGN(n) \ + (NUMERIC_IS_SHORT(n) ? \ + (((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \ + NUMERIC_NEG : NUMERIC_POS) : \ + (NUMERIC_IS_SPECIAL(n) ? \ + NUMERIC_EXT_FLAGBITS(n) : NUMERIC_FLAGBITS(n))) +#define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \ + ((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \ + >> NUMERIC_SHORT_DSCALE_SHIFT \ + : ((n)->choice.n_long.n_sign_dscale & NUMERIC_DSCALE_MASK)) + +#define NUMERIC_WEIGHT(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \ + (((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_SIGN_MASK ? \ + ~NUMERIC_SHORT_WEIGHT_MASK : 0) \ + | ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \ + : ((n)->choice.n_long.n_weight)) + +/* ---------- + * NumericVar is the format we use for arithmetic. The digit-array part + * is the same as the NumericData storage format, but the header is more + * complex. + * + * The value represented by a NumericVar is determined by the sign, weight, + * ndigits, and digits[] array. If it is a "special" value (NaN or Inf) + * then only the sign field matters; ndigits should be zero, and the weight + * and dscale fields are ignored. + * + * Note: the first digit of a NumericVar's value is assumed to be multiplied + * by NBASE ** weight. Another way to say it is that there are weight+1 + * digits before the decimal point. It is possible to have weight < 0. + * + * buf points at the physical start of the palloc'd digit buffer for the + * NumericVar. digits points at the first digit in actual use (the one + * with the specified weight). We normally leave an unused digit or two + * (preset to zeroes) between buf and digits, so that there is room to store + * a carry out of the top digit without reallocating space. We just need to + * decrement digits (and increment weight) to make room for the carry digit. + * (There is no such extra space in a numeric value stored in the database, + * only in a NumericVar in memory.) + * + * If buf is NULL then the digit buffer isn't actually palloc'd and should + * not be freed --- see the constants below for an example. + * + * dscale, or display scale, is the nominal precision expressed as number + * of digits after the decimal point (it must always be >= 0 at present). + * dscale may be more than the number of physically stored fractional digits, + * implying that we have suppressed storage of significant trailing zeroes. + * It should never be less than the number of stored digits, since that would + * imply hiding digits that are present. NOTE that dscale is always expressed + * in *decimal* digits, and so it may correspond to a fractional number of + * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits. + * + * rscale, or result scale, is the target precision for a computation. + * Like dscale it is expressed as number of *decimal* digits after the decimal + * point, and is always >= 0 at present. + * Note that rscale is not stored in variables --- it's figured on-the-fly + * from the dscales of the inputs. + * + * While we consistently use "weight" to refer to the base-NBASE weight of + * a numeric value, it is convenient in some scale-related calculations to + * make use of the base-10 weight (ie, the approximate log10 of the value). + * To avoid confusion, such a decimal-units weight is called a "dweight". + * + * NB: All the variable-level functions are written in a style that makes it + * possible to give one and the same variable as argument and destination. + * This is feasible because the digit buffer is separate from the variable. + * ---------- + */ +typedef struct NumericVar +{ + int ndigits; /* # of digits in digits[] - can be 0! */ + int weight; /* weight of first digit */ + int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */ + int dscale; /* display scale */ + NumericDigit *buf; /* start of palloc'd space for digits[] */ + NumericDigit *digits; /* base-NBASE digits */ +} NumericVar; + + +/* ---------- + * Local functions + * ---------- + */ + +#define digitbuf_alloc(ndigits) \ + ((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit))) +#define digitbuf_free(buf) \ + do { \ + if ((buf) != NULL) \ + pfree(buf); \ + } while (0) + +#define init_var(v) memset(v, 0, sizeof(NumericVar)) + +#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \ + (num)->choice.n_short.n_data : (num)->choice.n_long.n_data) +#define NUMERIC_NDIGITS(num) \ + ((VARSIZE(num) - NUMERIC_HEADER_SIZE(num)) / sizeof(NumericDigit)) +#define NUMERIC_CAN_BE_SHORT(scale,weight) \ + ((scale) <= NUMERIC_SHORT_DSCALE_MAX && \ + (weight) <= NUMERIC_SHORT_WEIGHT_MAX && \ + (weight) >= NUMERIC_SHORT_WEIGHT_MIN) + +static void alloc_var(NumericVar *var, int ndigits); +static void free_var(NumericVar *var); +static Numeric make_numeric(const struct NumericVar *var); + +/* + * alloc_var() - + * + * Allocate a digit buffer of ndigits digits (plus a spare digit for rounding) + */ +static void +alloc_var(NumericVar *var, int ndigits) +{ + digitbuf_free(var->buf); + var->buf = digitbuf_alloc(ndigits + 1); + var->buf[0] = 0; /* spare digit for rounding */ + var->digits = var->buf + 1; + var->ndigits = ndigits; +} + +/* + * free_var() - + * + * Return the digit buffer of a variable to the free pool + */ +static void +free_var(NumericVar *var) +{ + digitbuf_free(var->buf); + var->buf = NULL; + var->digits = NULL; + var->sign = NUMERIC_NAN; +} + +/* + * Convert 128 bit integer to numeric. + */ +static void +int128_to_numericvar(int128 val, NumericVar *var) +{ + uint128 uval, + newuval; + NumericDigit *ptr; + int ndigits; + + /* int128 can require at most 39 decimal digits; add one for safety */ + alloc_var(var, 40 / DEC_DIGITS); + if (val < 0) + { + var->sign = NUMERIC_NEG; + uval = -val; + } + else + { + var->sign = NUMERIC_POS; + uval = val; + } + var->dscale = 0; + if (val == 0) + { + var->ndigits = 0; + var->weight = 0; + return; + } + ptr = var->digits + var->ndigits; + ndigits = 0; + do + { + ptr--; + ndigits++; + newuval = uval / NBASE; + *ptr = uval - newuval * NBASE; + uval = newuval; + } while (uval); + var->digits = ptr; + var->ndigits = ndigits; + var->weight = ndigits - 1; +} + +/* + * make_result - + * + * Create the packed db numeric format in palloc()'d memory from + * a variable. This will handle NaN and Infinity cases. + * + */ +static Numeric +make_numeric(const struct NumericVar *var) +{ + Numeric result; + NumericDigit *digits = var->digits; + int weight = var->weight; + int sign = var->sign; + int n; + Size len; + + if ((sign & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL) + { + /* + * Verify valid special value. This could be just an Assert, perhaps, + * but it seems worthwhile to expend a few cycles to ensure that we + * never write any nonzero reserved bits to disk. + */ + if (!(sign == NUMERIC_NAN || + sign == NUMERIC_PINF || + sign == NUMERIC_NINF)) + elog(ERROR, "invalid numeric sign value 0x%x", sign); + + result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT); + + SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT); + result->choice.n_header = sign; + /* the header word is all we need */ + + return result; + } + + n = var->ndigits; + + /* truncate leading zeroes */ + while (n > 0 && *digits == 0) + { + digits++; + weight--; + n--; + } + /* truncate trailing zeroes */ + while (n > 0 && digits[n - 1] == 0) + n--; + + /* If zero result, force to weight=0 and positive sign */ + if (n == 0) + { + weight = 0; + sign = NUMERIC_POS; + } + + /* Build the result */ + if (NUMERIC_CAN_BE_SHORT(var->dscale, weight)) + { + len = NUMERIC_HDRSZ_SHORT + n * sizeof(NumericDigit); + result = (Numeric) palloc(len); + SET_VARSIZE(result, len); + result->choice.n_short.n_header = + (sign == NUMERIC_NEG ? (NUMERIC_SHORT | NUMERIC_SHORT_SIGN_MASK) + : NUMERIC_SHORT) + | (var->dscale << NUMERIC_SHORT_DSCALE_SHIFT) + | (weight < 0 ? NUMERIC_SHORT_WEIGHT_SIGN_MASK : 0) + | (weight & NUMERIC_SHORT_WEIGHT_MASK); + } + else + { + len = NUMERIC_HDRSZ + n * sizeof(NumericDigit); + result = (Numeric) palloc(len); + SET_VARSIZE(result, len); + result->choice.n_long.n_sign_dscale = + sign | (var->dscale & NUMERIC_DSCALE_MASK); + result->choice.n_long.n_weight = weight; + } + + Assert(NUMERIC_NDIGITS(result) == n); + if (n > 0) + memcpy(NUMERIC_DIGITS(result), digits, n * sizeof(NumericDigit)); + + /* Check for overflow of int16 fields */ + if (NUMERIC_WEIGHT(result) != weight || + NUMERIC_DSCALE(result) != var->dscale) + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + + } + + return result; +} + +Numeric int128_to_numeric(int128 val) +{ + Numeric res; + NumericVar result; + + init_var(&result); + + int128_to_numericvar(val, &result); + + res = make_numeric(&result); + + free_var(&result); + + return res; +} diff --git a/columnar/src/include/columnar/columnar.h b/columnar/src/include/columnar/columnar.h index c591c8a0..d987b637 100644 --- a/columnar/src/include/columnar/columnar.h +++ b/columnar/src/include/columnar/columnar.h @@ -17,6 +17,7 @@ #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/parsenodes.h" +#include "nodes/extensible.h" #include "port/atomics.h" #include "storage/bufpage.h" #include "storage/lockdefs.h" @@ -260,7 +261,6 @@ extern int columnar_page_cache_size; typedef void (*ColumnarTableSetOptions_hook_type)(Oid relid, ColumnarOptions options); extern void columnar_init(void); -extern void columnar_init_gucs(void); extern CompressionType ParseCompressionType(const char *compressionTypeString); @@ -376,6 +376,9 @@ extern bytea * ReadChunkRowMask(RelFileNode relfilenode, Snapshot snapshot, uint64 stripeFirstRowNumber, int rowCount); extern Datum create_table_row_mask(PG_FUNCTION_ARGS); +/* columnar_planner_hook.c */ +extern void columnar_planner_init(void); + /* write_state_interface.c */ extern void FlushWriteStateWithNewSnapshot(Oid relfilenode, Snapshot * snapshot, diff --git a/columnar/src/include/columnar/columnar_customscan.h b/columnar/src/include/columnar/columnar_customscan.h index 213ab39d..e8c7fe3d 100644 --- a/columnar/src/include/columnar/columnar_customscan.h +++ b/columnar/src/include/columnar/columnar_customscan.h @@ -13,7 +13,12 @@ #ifndef COLUMNAR_CUSTOMSCAN_H #define COLUMNAR_CUSTOMSCAN_H -void columnar_customscan_init(void); +#include "nodes/extensible.h" +/* Flag to indicate is vectorized aggregate used in execution */ +#define CUSTOM_SCAN_VECTORIZED_AGGREGATE 1 + +extern void columnar_customscan_init(void); +extern const CustomScanMethods * columnar_customscan_methods(void); #endif /* COLUMNAR_CUSTOMSCAN_H */ diff --git a/columnar/src/include/columnar/utils/listutils.h b/columnar/src/include/columnar/utils/listutils.h index f8b0b609..f251f10b 100644 --- a/columnar/src/include/columnar/utils/listutils.h +++ b/columnar/src/include/columnar/utils/listutils.h @@ -16,9 +16,15 @@ #include "c.h" #include "nodes/pg_list.h" -#include "pg_version_compat.h" + #include "utils/array.h" #include "utils/hsearch.h" +#include "nodes/makefuncs.h" +#include "nodes/nodes.h" +#include "nodes/nodeFuncs.h" +#include "nodes/primnodes.h" + +#include "pg_version_compat.h" /* @@ -166,15 +172,32 @@ typedef struct ListCellAndListWrapper #define foreach_ptr_append(var, l) foreach_ptr(var, l) #endif -/* utility functions declaration shared within this module */ -extern List * SortList(List *pointerList, - int (*ComparisonFunction)(const void *, const void *)); -extern void ** PointerArrayFromList(List *pointerList); -extern HTAB * ListToHashSet(List *pointerList, Size keySize, bool isStringList); -extern char * StringJoin(List *stringList, char delimiter); -extern List * ListTake(List *pointerList, int size); -extern void * safe_list_nth(const List *list, int index); -extern List * GeneratePositiveIntSequenceList(int upTo); -extern List * GenerateListFromElement(void *listElement, int listLength); +static inline List* +CustomBuildTargetList(List* tlist, Index varNo) +{ + List *result_tlist = NIL; + ListCell *lc; + + foreach (lc, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + Var *var = makeVar(varNo, + tle->resno, + exprType((Node *) tle->expr), + exprTypmod((Node *) tle->expr), + exprCollation((Node *) tle->expr), + 0); + + TargetEntry *newtle = makeTargetEntry((Expr *) var, + tle->resno, + tle->resname, + tle->resjunk); + + result_tlist = lappend(result_tlist, newtle); + } + + return result_tlist; +} #endif /* CITUS_LISTUTILS_H */ diff --git a/columnar/src/include/columnar/vectorization/columnar_vector_execution.h b/columnar/src/include/columnar/vectorization/columnar_vector_execution.h index 6cbff81a..f9437198 100644 --- a/columnar/src/include/columnar/vectorization/columnar_vector_execution.h +++ b/columnar/src/include/columnar/vectorization/columnar_vector_execution.h @@ -16,6 +16,8 @@ #include "nodes/pg_list.h" #include "nodes/primnodes.h" +extern bool CheckOpExprArgumentRules(List *args); +extern bool GetVectorizedProcedureOid(Oid procedureOid, Oid *vectorizedProcedureOid); extern List * CreateVectorizedExprList(List *exprList); extern List * ConstructVectorizedQualList(TupleTableSlot *slot, List *vectorizedQual); extern bool * ExecuteVectorizedQual(TupleTableSlot *slot, diff --git a/columnar/src/include/columnar/vectorization/columnar_vector_types.h b/columnar/src/include/columnar/vectorization/columnar_vector_types.h index 5d9a38ee..c35e4ea5 100644 --- a/columnar/src/include/columnar/vectorization/columnar_vector_types.h +++ b/columnar/src/include/columnar/vectorization/columnar_vector_types.h @@ -28,8 +28,8 @@ typedef struct VectorTupleTableSlot TupleTableSlot tts; /* How many tuples does this slot contain */ uint32 dimension; - /* Skip array to represent filtered tuples */ - bool skip[COLUMNAR_VECTOR_COLUMN_SIZE]; + /* Keep array to represent filtered tuples */ + bool keep[COLUMNAR_VECTOR_COLUMN_SIZE]; /* Row Number */ uint64 rowNumber[COLUMNAR_VECTOR_COLUMN_SIZE]; } VectorTupleTableSlot; @@ -50,10 +50,14 @@ extern VectorColumn * BuildVectorColumn(int16 columnDimension, int16 columnTypeLen, bool columnIsVal, uint64 *rowNumber); -extern void extractTupleFromVectorSlot(TupleTableSlot *out, +extern void ExtractTupleFromVectorSlot(TupleTableSlot *out, VectorTupleTableSlot *vectorSlot, int32 index, - Bitmapset *attrNeeded); + List *attrNeededList); +extern void WriteTupleToVectorSlot(TupleTableSlot *in, + VectorTupleTableSlot *vectorSlot, + int32 index); +extern void CleanupVectorSlot(VectorTupleTableSlot *vectorSlot); typedef enum VectorQualType { diff --git a/columnar/src/include/columnar/vectorization/nodes/columnar_aggregator_node.h b/columnar/src/include/columnar/vectorization/nodes/columnar_aggregator_node.h new file mode 100644 index 00000000..2cad8008 --- /dev/null +++ b/columnar/src/include/columnar/vectorization/nodes/columnar_aggregator_node.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * columnar_aggregator_node.h + * Custom scan method for aggregation + * + * IDENTIFICATION + * src/backend/columnar/vectorization/nodes/columnar_aggregator_node.c + * + *------------------------------------------------------------------------- + */ + + +#ifndef COLUMNAR_AGGEREGATOR_NODE_H +#define COLUMNAR_AGGEREGATOR_NODE_H + +#include "postgres.h" + +#include "nodes/execnodes.h" + +typedef struct VectorAggState +{ + CustomScanState css; + AggState *aggstate; +} VectorAggState; + +extern CustomScan *columnar_create_aggregator_node(void); +extern void columnar_register_aggregator_node(void); + +#endif diff --git a/columnar/src/include/columnar/vectorization/types/numeric.h b/columnar/src/include/columnar/vectorization/types/numeric.h new file mode 100644 index 00000000..415588ed --- /dev/null +++ b/columnar/src/include/columnar/vectorization/types/numeric.h @@ -0,0 +1,19 @@ +/*------------------------------------------------------------------------- + * + * numeric.h + * + * IDENTIFICATION + * src/backend/columnar/vectorization/types/numeric.c + * + *------------------------------------------------------------------------- + */ + +#ifndef VECTORIZATION_TYPE_INT128_H +#define VECTORIZATION_TYPE_INT128_H + +#include "postgres.h" + +#include "utils/numeric.h" +extern Numeric int128_to_numeric(int128 val); + +#endif \ No newline at end of file diff --git a/columnar/src/include/columnar/vectorization/types/types.h b/columnar/src/include/columnar/vectorization/types/types.h index 39280a9d..76d1ad2a 100644 --- a/columnar/src/include/columnar/vectorization/types/types.h +++ b/columnar/src/include/columnar/vectorization/types/types.h @@ -85,5 +85,20 @@ Datum v##FNAME##OPSTR(PG_FUNCTION_ARGS) \ _BUILD_CMP_OP_INT(FNAME, LTYPE, RTYPE, <=, le) \ _BUILD_CMP_OP_INT(FNAME, LTYPE, RTYPE, >=, ge) \ + +typedef struct Int128AggState +{ + bool calcSumX2; /* if true, calculate sumX2 */ + int64 N; /* count of processed numbers */ + int128 sumX; /* sum of processed numbers */ + int128 sumX2; /* sum of squares of processed numbers */ +} Int128AggState; + +typedef struct Int64AggState +{ + int64 N; /* count of processed numbers */ + int64 sumX; /* sum of processed numbers */ +} Int64AggState; + #endif diff --git a/columnar/src/test/regress/columnar_schedule b/columnar/src/test/regress/columnar_schedule index 3b458457..aac1a3cd 100644 --- a/columnar/src/test/regress/columnar_schedule +++ b/columnar/src/test/regress/columnar_schedule @@ -34,3 +34,4 @@ test: columnar_matview #test: columnar_memory test: columnar_alter_table_set_access_method test: columnar_cache +test: columnar_aggregates diff --git a/columnar/src/test/regress/expected/columnar_aggregates.out b/columnar/src/test/regress/expected/columnar_aggregates.out new file mode 100644 index 00000000..eecf243f --- /dev/null +++ b/columnar/src/test/regress/expected/columnar_aggregates.out @@ -0,0 +1,241 @@ +-- Custom direct aggregates implementation +-- 1. SMALLINT +CREATE TABLE t_smallint(a SMALLINT) USING columnar; +INSERT INTO t_smallint SELECT g % 16384 FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Finalize Aggregate + Output: vcount(*), vsum(a), vavg(a), vmin(a), vmax(a) + -> Gather + Output: (PARTIAL vcount(*)), (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + Workers Planned: 7 + -> Parallel Custom Scan (VectorAggNode) + Output: (PARTIAL vcount(*)), (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + -> Parallel Custom Scan (ColumnarScan) on public.t_smallint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + sum | avg | min | max +-------------+-----------------------+-----+------- + 24561838944 | 8187.2769189076936974 | 0 | 16383 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Finalize Aggregate + Output: count(*), sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL count(*)), (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL count(*), PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_smallint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + sum | avg | min | max +-------------+-----------------------+-----+------- + 24561838944 | 8187.2769189076936974 | 0 | 16383 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_smallint; +-- 2. INT +CREATE TABLE t_int(a INT) USING columnar; +INSERT INTO t_int SELECT g FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Finalize Aggregate + Output: vsum(a), vavg(a), vmin(a), vmax(a) + -> Gather + Output: (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + Workers Planned: 7 + -> Parallel Custom Scan (VectorAggNode) + Output: (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + -> Parallel Custom Scan (ColumnarScan) on public.t_int + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_int + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_int; +-- 3. BIGINT +CREATE TABLE t_bigint(a BIGINT) USING columnar; +INSERT INTO t_bigint SELECT g FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Finalize Aggregate + Output: vsum(a), vavg(a), vmin(a), vmax(a) + -> Gather + Output: (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + Workers Planned: 7 + -> Parallel Custom Scan (VectorAggNode) + Output: (PARTIAL vsum(a)), (PARTIAL vavg(a)), (PARTIAL vmin(a)), (PARTIAL vmax(a)) + -> Parallel Custom Scan (ColumnarScan) on public.t_bigint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_bigint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_bigint; +-- 4. DATE +CREATE TABLE t_date(a DATE) USING columnar; +INSERT INTO t_date VALUES ('2000-01-01'), ('2020-01-01'), ('2010-01-01'), ('2000-01-02'); +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + QUERY PLAN +--------------------------------------------------- + Custom Scan (VectorAggNode) + Output: (vmin(a)), (vmax(a)) + -> Custom Scan (ColumnarScan) on public.t_date + Output: a + Columnar Projected Columns: a +(5 rows) + +SELECT MIN(a), MAX(a) FROM t_date; + min | max +------------+------------ + 01-01-2000 | 01-01-2020 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + QUERY PLAN +--------------------------------------------------- + Aggregate + Output: min(a), max(a) + -> Custom Scan (ColumnarScan) on public.t_date + Output: a + Columnar Projected Columns: a +(5 rows) + +SELECT MIN(a), MAX(a) FROM t_date; + min | max +------------+------------ + 01-01-2000 | 01-01-2020 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_date; +-- Test exception when we fallback to PG aggregator node +SET client_min_messages TO 'DEBUG1'; +CREATE TABLE t_mixed(a INT, b BIGINT, c DATE, d TIME) using columnar; +INSERT INTO t_mixed VALUES (0, 1000, '2000-01-01', '23:50'), (10, 2000, '2010-01-01', '00:50'); +DEBUG: Flushing Stripe of size 2 +-- Vectorized aggregate not found (TIME aggregates are not implemented) +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(d) FROM t_mixed; +DEBUG: Query can't be vectorized. Falling back to original execution. +DETAIL: Vectorized aggregate not found. + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: min(d) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: d + Columnar Projected Columns: d +(5 rows) + +--Unsupported aggregate argument combination. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a + b) FROM t_mixed; +DEBUG: Query can't be vectorized. Falling back to original execution. +DETAIL: Unsupported aggregate argument combination. + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: sum((a + b)) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: a, b + Columnar Projected Columns: a, b +(5 rows) + +-- Vectorized Aggregates accepts only non-const values. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(1) FROM t_mixed; +DEBUG: Query can't be vectorized. Falling back to original execution. +DETAIL: Vectorized Aggregates accepts only non-const values. + QUERY PLAN +-------------------------------------------------------------------------- + Aggregate + Output: count(1) + -> Custom Scan (ColumnarScan) on public.t_mixed + Columnar Projected Columns: +(4 rows) + +-- Vectorized aggregate with DISTINCT not supported. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(DISTINCT a) FROM t_mixed; +DEBUG: Query can't be vectorized. Falling back to original execution. +DETAIL: Vectorized aggregate with DISTINCT not supported. + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: count(DISTINCT a) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: a + Columnar Projected Columns: a +(5 rows) + +DROP TABLE t_mixed; +SET client_min_messages TO default; diff --git a/columnar/src/test/regress/expected/columnar_aggregates_1.out b/columnar/src/test/regress/expected/columnar_aggregates_1.out new file mode 100644 index 00000000..48441bbb --- /dev/null +++ b/columnar/src/test/regress/expected/columnar_aggregates_1.out @@ -0,0 +1,233 @@ +-- Custom direct aggregates implementation +-- 1. SMALLINT +CREATE TABLE t_smallint(a SMALLINT) USING columnar; +INSERT INTO t_smallint SELECT g % 16384 FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Finalize Aggregate + Output: count(*), sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL count(*)), (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL count(*), PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_smallint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + sum | avg | min | max +-------------+-----------------------+-----+------- + 24561838944 | 8187.2769189076936974 | 0 | 16383 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Finalize Aggregate + Output: count(*), sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL count(*)), (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL count(*), PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_smallint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + sum | avg | min | max +-------------+-----------------------+-----+------- + 24561838944 | 8187.2769189076936974 | 0 | 16383 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_smallint; +-- 2. INT +CREATE TABLE t_int(a INT) USING columnar; +INSERT INTO t_int SELECT g FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_int + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_int + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_int; +-- 3. BIGINT +CREATE TABLE t_bigint(a BIGINT) USING columnar; +INSERT INTO t_bigint SELECT g FROM GENERATE_SERIES(0, 3000000) g; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_bigint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + QUERY PLAN +---------------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(a), avg(a), min(a), max(a) + -> Gather + Output: (PARTIAL sum(a)), (PARTIAL avg(a)), (PARTIAL min(a)), (PARTIAL max(a)) + Workers Planned: 7 + -> Partial Aggregate + Output: PARTIAL sum(a), PARTIAL avg(a), PARTIAL min(a), PARTIAL max(a) + -> Parallel Custom Scan (ColumnarScan) on public.t_bigint + Output: a + Columnar Projected Columns: a +(10 rows) + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + sum | avg | min | max +---------------+----------------------+-----+--------- + 4500001500000 | 1500000.000000000000 | 0 | 3000000 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_bigint; +-- 4. DATE +CREATE TABLE t_date(a DATE) USING columnar; +INSERT INTO t_date VALUES ('2000-01-01'), ('2020-01-01'), ('2010-01-01'), ('2000-01-02'); +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + QUERY PLAN +--------------------------------------------------- + Aggregate + Output: min(a), max(a) + -> Custom Scan (ColumnarScan) on public.t_date + Output: a + Columnar Projected Columns: a +(5 rows) + +SELECT MIN(a), MAX(a) FROM t_date; + min | max +------------+------------ + 01-01-2000 | 01-01-2020 +(1 row) + +SET columnar.enable_vectorization TO false; +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + QUERY PLAN +--------------------------------------------------- + Aggregate + Output: min(a), max(a) + -> Custom Scan (ColumnarScan) on public.t_date + Output: a + Columnar Projected Columns: a +(5 rows) + +SELECT MIN(a), MAX(a) FROM t_date; + min | max +------------+------------ + 01-01-2000 | 01-01-2020 +(1 row) + +SET columnar.enable_vectorization TO default; +DROP TABLE t_date; +-- Test exception when we fallback to PG aggregator node +SET client_min_messages TO 'DEBUG1'; +CREATE TABLE t_mixed(a INT, b BIGINT, c DATE, d TIME) using columnar; +INSERT INTO t_mixed VALUES (0, 1000, '2000-01-01', '23:50'), (10, 2000, '2010-01-01', '00:50'); +DEBUG: Flushing Stripe of size 2 +-- Vectorized aggregate not found (TIME aggregates are not implemented) +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(d) FROM t_mixed; + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: min(d) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: d + Columnar Projected Columns: d +(5 rows) + +--Unsupported aggregate argument combination. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a + b) FROM t_mixed; + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: sum((a + b)) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: a, b + Columnar Projected Columns: a, b +(5 rows) + +-- Vectorized Aggregates accepts only non-const values. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(1) FROM t_mixed; + QUERY PLAN +-------------------------------------------------------------------------- + Aggregate + Output: count(1) + -> Custom Scan (ColumnarScan) on public.t_mixed + Columnar Projected Columns: +(4 rows) + +-- Vectorized aggregate with DISTINCT not supported. +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(DISTINCT a) FROM t_mixed; + QUERY PLAN +---------------------------------------------------- + Aggregate + Output: count(DISTINCT a) + -> Custom Scan (ColumnarScan) on public.t_mixed + Output: a + Columnar Projected Columns: a +(5 rows) + +DROP TABLE t_mixed; +SET client_min_messages TO default; diff --git a/columnar/src/test/regress/expected/columnar_cache.out b/columnar/src/test/regress/expected/columnar_cache.out index 974f85df..ebf6c9d8 100644 --- a/columnar/src/test/regress/expected/columnar_cache.out +++ b/columnar/src/test/regress/expected/columnar_cache.out @@ -3130,7 +3130,7 @@ EXPLAIN SELECT COUNT(*) FROM t1; Finalize Aggregate (cost=1472.17..1472.18 rows=1 width=8) -> Gather (cost=1471.54..1472.15 rows=6 width=8) Workers Planned: 6 - -> Partial Aggregate (cost=471.54..471.55 rows=1 width=8) + -> Parallel Custom Scan (VectorAggNode) (cost=471.54..471.55 rows=1 width=8) -> Parallel Custom Scan (ColumnarScan) on t1 (cost=0.00..54.88 rows=166667 width=0) Columnar Projected Columns: (6 rows) diff --git a/columnar/src/test/regress/expected/columnar_cache_1.out b/columnar/src/test/regress/expected/columnar_cache_1.out new file mode 100644 index 00000000..974f85df --- /dev/null +++ b/columnar/src/test/regress/expected/columnar_cache_1.out @@ -0,0 +1,3138 @@ +CREATE TABLE big_table ( + id INT, + firstname TEXT, + lastname TEXT +) USING columnar; +INSERT INTO big_table (id, firstname, lastname) + SELECT i, + CONCAT('firstname-', i), + CONCAT('lastname-', i) + FROM generate_series(1, 1000000) as i; +-- get some baselines from multiple chunks +SELECT firstname, + lastname, + SUM(id) + FROM big_table + WHERE id < 1000 + GROUP BY firstname, + lastname +UNION +SELECT firstname, + lastname, + SUM(id) + FROM big_table + WHERE id BETWEEN 15000 AND 16000 + GROUP BY firstname, + lastname + ORDER BY firstname; + firstname | lastname | sum +-----------------+----------------+------- + firstname-1 | lastname-1 | 1 + firstname-10 | lastname-10 | 10 + firstname-100 | lastname-100 | 100 + firstname-101 | lastname-101 | 101 + firstname-102 | lastname-102 | 102 + firstname-103 | lastname-103 | 103 + firstname-104 | lastname-104 | 104 + firstname-105 | lastname-105 | 105 + firstname-106 | lastname-106 | 106 + firstname-107 | lastname-107 | 107 + firstname-108 | lastname-108 | 108 + firstname-109 | lastname-109 | 109 + firstname-11 | lastname-11 | 11 + firstname-110 | lastname-110 | 110 + firstname-111 | lastname-111 | 111 + firstname-112 | lastname-112 | 112 + firstname-113 | lastname-113 | 113 + firstname-114 | lastname-114 | 114 + firstname-115 | lastname-115 | 115 + firstname-116 | lastname-116 | 116 + firstname-117 | lastname-117 | 117 + firstname-118 | lastname-118 | 118 + firstname-119 | lastname-119 | 119 + firstname-12 | lastname-12 | 12 + firstname-120 | lastname-120 | 120 + firstname-121 | lastname-121 | 121 + firstname-122 | lastname-122 | 122 + firstname-123 | lastname-123 | 123 + firstname-124 | lastname-124 | 124 + firstname-125 | lastname-125 | 125 + firstname-126 | lastname-126 | 126 + firstname-127 | lastname-127 | 127 + firstname-128 | lastname-128 | 128 + firstname-129 | lastname-129 | 129 + firstname-13 | lastname-13 | 13 + firstname-130 | lastname-130 | 130 + firstname-131 | lastname-131 | 131 + firstname-132 | lastname-132 | 132 + firstname-133 | lastname-133 | 133 + firstname-134 | lastname-134 | 134 + firstname-135 | lastname-135 | 135 + firstname-136 | lastname-136 | 136 + firstname-137 | lastname-137 | 137 + firstname-138 | lastname-138 | 138 + firstname-139 | lastname-139 | 139 + firstname-14 | lastname-14 | 14 + firstname-140 | lastname-140 | 140 + firstname-141 | lastname-141 | 141 + firstname-142 | lastname-142 | 142 + firstname-143 | lastname-143 | 143 + firstname-144 | lastname-144 | 144 + firstname-145 | lastname-145 | 145 + firstname-146 | lastname-146 | 146 + firstname-147 | lastname-147 | 147 + firstname-148 | lastname-148 | 148 + firstname-149 | lastname-149 | 149 + firstname-15 | lastname-15 | 15 + firstname-150 | lastname-150 | 150 + firstname-15000 | lastname-15000 | 15000 + firstname-15001 | lastname-15001 | 15001 + firstname-15002 | lastname-15002 | 15002 + firstname-15003 | lastname-15003 | 15003 + firstname-15004 | lastname-15004 | 15004 + firstname-15005 | lastname-15005 | 15005 + firstname-15006 | lastname-15006 | 15006 + firstname-15007 | lastname-15007 | 15007 + firstname-15008 | lastname-15008 | 15008 + firstname-15009 | lastname-15009 | 15009 + firstname-15010 | lastname-15010 | 15010 + firstname-15011 | lastname-15011 | 15011 + firstname-15012 | lastname-15012 | 15012 + firstname-15013 | lastname-15013 | 15013 + firstname-15014 | lastname-15014 | 15014 + firstname-15015 | lastname-15015 | 15015 + firstname-15016 | lastname-15016 | 15016 + firstname-15017 | lastname-15017 | 15017 + firstname-15018 | lastname-15018 | 15018 + firstname-15019 | lastname-15019 | 15019 + firstname-15020 | lastname-15020 | 15020 + firstname-15021 | lastname-15021 | 15021 + firstname-15022 | lastname-15022 | 15022 + firstname-15023 | lastname-15023 | 15023 + firstname-15024 | lastname-15024 | 15024 + firstname-15025 | lastname-15025 | 15025 + firstname-15026 | lastname-15026 | 15026 + firstname-15027 | lastname-15027 | 15027 + firstname-15028 | lastname-15028 | 15028 + firstname-15029 | lastname-15029 | 15029 + firstname-15030 | lastname-15030 | 15030 + firstname-15031 | lastname-15031 | 15031 + firstname-15032 | lastname-15032 | 15032 + firstname-15033 | lastname-15033 | 15033 + firstname-15034 | lastname-15034 | 15034 + firstname-15035 | lastname-15035 | 15035 + firstname-15036 | lastname-15036 | 15036 + firstname-15037 | lastname-15037 | 15037 + firstname-15038 | lastname-15038 | 15038 + firstname-15039 | lastname-15039 | 15039 + firstname-15040 | lastname-15040 | 15040 + firstname-15041 | lastname-15041 | 15041 + firstname-15042 | lastname-15042 | 15042 + firstname-15043 | lastname-15043 | 15043 + firstname-15044 | lastname-15044 | 15044 + firstname-15045 | lastname-15045 | 15045 + firstname-15046 | lastname-15046 | 15046 + firstname-15047 | lastname-15047 | 15047 + firstname-15048 | lastname-15048 | 15048 + firstname-15049 | lastname-15049 | 15049 + firstname-15050 | lastname-15050 | 15050 + firstname-15051 | lastname-15051 | 15051 + firstname-15052 | lastname-15052 | 15052 + firstname-15053 | lastname-15053 | 15053 + firstname-15054 | lastname-15054 | 15054 + firstname-15055 | lastname-15055 | 15055 + firstname-15056 | lastname-15056 | 15056 + firstname-15057 | lastname-15057 | 15057 + firstname-15058 | lastname-15058 | 15058 + firstname-15059 | lastname-15059 | 15059 + firstname-15060 | lastname-15060 | 15060 + firstname-15061 | lastname-15061 | 15061 + firstname-15062 | lastname-15062 | 15062 + firstname-15063 | lastname-15063 | 15063 + firstname-15064 | lastname-15064 | 15064 + firstname-15065 | lastname-15065 | 15065 + firstname-15066 | lastname-15066 | 15066 + firstname-15067 | lastname-15067 | 15067 + firstname-15068 | lastname-15068 | 15068 + firstname-15069 | lastname-15069 | 15069 + firstname-15070 | lastname-15070 | 15070 + firstname-15071 | lastname-15071 | 15071 + firstname-15072 | lastname-15072 | 15072 + firstname-15073 | lastname-15073 | 15073 + firstname-15074 | lastname-15074 | 15074 + firstname-15075 | lastname-15075 | 15075 + firstname-15076 | lastname-15076 | 15076 + firstname-15077 | lastname-15077 | 15077 + firstname-15078 | lastname-15078 | 15078 + firstname-15079 | lastname-15079 | 15079 + firstname-15080 | lastname-15080 | 15080 + firstname-15081 | lastname-15081 | 15081 + firstname-15082 | lastname-15082 | 15082 + firstname-15083 | lastname-15083 | 15083 + firstname-15084 | lastname-15084 | 15084 + firstname-15085 | lastname-15085 | 15085 + firstname-15086 | lastname-15086 | 15086 + firstname-15087 | lastname-15087 | 15087 + firstname-15088 | lastname-15088 | 15088 + firstname-15089 | lastname-15089 | 15089 + firstname-15090 | lastname-15090 | 15090 + firstname-15091 | lastname-15091 | 15091 + firstname-15092 | lastname-15092 | 15092 + firstname-15093 | lastname-15093 | 15093 + firstname-15094 | lastname-15094 | 15094 + firstname-15095 | lastname-15095 | 15095 + firstname-15096 | lastname-15096 | 15096 + firstname-15097 | lastname-15097 | 15097 + firstname-15098 | lastname-15098 | 15098 + firstname-15099 | lastname-15099 | 15099 + firstname-151 | lastname-151 | 151 + firstname-15100 | lastname-15100 | 15100 + firstname-15101 | lastname-15101 | 15101 + firstname-15102 | lastname-15102 | 15102 + firstname-15103 | lastname-15103 | 15103 + firstname-15104 | lastname-15104 | 15104 + firstname-15105 | lastname-15105 | 15105 + firstname-15106 | lastname-15106 | 15106 + firstname-15107 | lastname-15107 | 15107 + firstname-15108 | lastname-15108 | 15108 + firstname-15109 | lastname-15109 | 15109 + firstname-15110 | lastname-15110 | 15110 + firstname-15111 | lastname-15111 | 15111 + firstname-15112 | lastname-15112 | 15112 + firstname-15113 | lastname-15113 | 15113 + firstname-15114 | lastname-15114 | 15114 + firstname-15115 | lastname-15115 | 15115 + firstname-15116 | lastname-15116 | 15116 + firstname-15117 | lastname-15117 | 15117 + firstname-15118 | lastname-15118 | 15118 + firstname-15119 | lastname-15119 | 15119 + firstname-15120 | lastname-15120 | 15120 + firstname-15121 | lastname-15121 | 15121 + firstname-15122 | lastname-15122 | 15122 + firstname-15123 | lastname-15123 | 15123 + firstname-15124 | lastname-15124 | 15124 + firstname-15125 | lastname-15125 | 15125 + firstname-15126 | lastname-15126 | 15126 + firstname-15127 | lastname-15127 | 15127 + firstname-15128 | lastname-15128 | 15128 + firstname-15129 | lastname-15129 | 15129 + firstname-15130 | lastname-15130 | 15130 + firstname-15131 | lastname-15131 | 15131 + firstname-15132 | lastname-15132 | 15132 + firstname-15133 | lastname-15133 | 15133 + firstname-15134 | lastname-15134 | 15134 + firstname-15135 | lastname-15135 | 15135 + firstname-15136 | lastname-15136 | 15136 + firstname-15137 | lastname-15137 | 15137 + firstname-15138 | lastname-15138 | 15138 + firstname-15139 | lastname-15139 | 15139 + firstname-15140 | lastname-15140 | 15140 + firstname-15141 | lastname-15141 | 15141 + firstname-15142 | lastname-15142 | 15142 + firstname-15143 | lastname-15143 | 15143 + firstname-15144 | lastname-15144 | 15144 + firstname-15145 | lastname-15145 | 15145 + firstname-15146 | lastname-15146 | 15146 + firstname-15147 | lastname-15147 | 15147 + firstname-15148 | lastname-15148 | 15148 + firstname-15149 | lastname-15149 | 15149 + firstname-15150 | lastname-15150 | 15150 + firstname-15151 | lastname-15151 | 15151 + firstname-15152 | lastname-15152 | 15152 + firstname-15153 | lastname-15153 | 15153 + firstname-15154 | lastname-15154 | 15154 + firstname-15155 | lastname-15155 | 15155 + firstname-15156 | lastname-15156 | 15156 + firstname-15157 | lastname-15157 | 15157 + firstname-15158 | lastname-15158 | 15158 + firstname-15159 | lastname-15159 | 15159 + firstname-15160 | lastname-15160 | 15160 + firstname-15161 | lastname-15161 | 15161 + firstname-15162 | lastname-15162 | 15162 + firstname-15163 | lastname-15163 | 15163 + firstname-15164 | lastname-15164 | 15164 + firstname-15165 | lastname-15165 | 15165 + firstname-15166 | lastname-15166 | 15166 + firstname-15167 | lastname-15167 | 15167 + firstname-15168 | lastname-15168 | 15168 + firstname-15169 | lastname-15169 | 15169 + firstname-15170 | lastname-15170 | 15170 + firstname-15171 | lastname-15171 | 15171 + firstname-15172 | lastname-15172 | 15172 + firstname-15173 | lastname-15173 | 15173 + firstname-15174 | lastname-15174 | 15174 + firstname-15175 | lastname-15175 | 15175 + firstname-15176 | lastname-15176 | 15176 + firstname-15177 | lastname-15177 | 15177 + firstname-15178 | lastname-15178 | 15178 + firstname-15179 | lastname-15179 | 15179 + firstname-15180 | lastname-15180 | 15180 + firstname-15181 | lastname-15181 | 15181 + firstname-15182 | lastname-15182 | 15182 + firstname-15183 | lastname-15183 | 15183 + firstname-15184 | lastname-15184 | 15184 + firstname-15185 | lastname-15185 | 15185 + firstname-15186 | lastname-15186 | 15186 + firstname-15187 | lastname-15187 | 15187 + firstname-15188 | lastname-15188 | 15188 + firstname-15189 | lastname-15189 | 15189 + firstname-15190 | lastname-15190 | 15190 + firstname-15191 | lastname-15191 | 15191 + firstname-15192 | lastname-15192 | 15192 + firstname-15193 | lastname-15193 | 15193 + firstname-15194 | lastname-15194 | 15194 + firstname-15195 | lastname-15195 | 15195 + firstname-15196 | lastname-15196 | 15196 + firstname-15197 | lastname-15197 | 15197 + firstname-15198 | lastname-15198 | 15198 + firstname-15199 | lastname-15199 | 15199 + firstname-152 | lastname-152 | 152 + firstname-15200 | lastname-15200 | 15200 + firstname-15201 | lastname-15201 | 15201 + firstname-15202 | lastname-15202 | 15202 + firstname-15203 | lastname-15203 | 15203 + firstname-15204 | lastname-15204 | 15204 + firstname-15205 | lastname-15205 | 15205 + firstname-15206 | lastname-15206 | 15206 + firstname-15207 | lastname-15207 | 15207 + firstname-15208 | lastname-15208 | 15208 + firstname-15209 | lastname-15209 | 15209 + firstname-15210 | lastname-15210 | 15210 + firstname-15211 | lastname-15211 | 15211 + firstname-15212 | lastname-15212 | 15212 + firstname-15213 | lastname-15213 | 15213 + firstname-15214 | lastname-15214 | 15214 + firstname-15215 | lastname-15215 | 15215 + firstname-15216 | lastname-15216 | 15216 + firstname-15217 | lastname-15217 | 15217 + firstname-15218 | lastname-15218 | 15218 + firstname-15219 | lastname-15219 | 15219 + firstname-15220 | lastname-15220 | 15220 + firstname-15221 | lastname-15221 | 15221 + firstname-15222 | lastname-15222 | 15222 + firstname-15223 | lastname-15223 | 15223 + firstname-15224 | lastname-15224 | 15224 + firstname-15225 | lastname-15225 | 15225 + firstname-15226 | lastname-15226 | 15226 + firstname-15227 | lastname-15227 | 15227 + firstname-15228 | lastname-15228 | 15228 + firstname-15229 | lastname-15229 | 15229 + firstname-15230 | lastname-15230 | 15230 + firstname-15231 | lastname-15231 | 15231 + firstname-15232 | lastname-15232 | 15232 + firstname-15233 | lastname-15233 | 15233 + firstname-15234 | lastname-15234 | 15234 + firstname-15235 | lastname-15235 | 15235 + firstname-15236 | lastname-15236 | 15236 + firstname-15237 | lastname-15237 | 15237 + firstname-15238 | lastname-15238 | 15238 + firstname-15239 | lastname-15239 | 15239 + firstname-15240 | lastname-15240 | 15240 + firstname-15241 | lastname-15241 | 15241 + firstname-15242 | lastname-15242 | 15242 + firstname-15243 | lastname-15243 | 15243 + firstname-15244 | lastname-15244 | 15244 + firstname-15245 | lastname-15245 | 15245 + firstname-15246 | lastname-15246 | 15246 + firstname-15247 | lastname-15247 | 15247 + firstname-15248 | lastname-15248 | 15248 + firstname-15249 | lastname-15249 | 15249 + firstname-15250 | lastname-15250 | 15250 + firstname-15251 | lastname-15251 | 15251 + firstname-15252 | lastname-15252 | 15252 + firstname-15253 | lastname-15253 | 15253 + firstname-15254 | lastname-15254 | 15254 + firstname-15255 | lastname-15255 | 15255 + firstname-15256 | lastname-15256 | 15256 + firstname-15257 | lastname-15257 | 15257 + firstname-15258 | lastname-15258 | 15258 + firstname-15259 | lastname-15259 | 15259 + firstname-15260 | lastname-15260 | 15260 + firstname-15261 | lastname-15261 | 15261 + firstname-15262 | lastname-15262 | 15262 + firstname-15263 | lastname-15263 | 15263 + firstname-15264 | lastname-15264 | 15264 + firstname-15265 | lastname-15265 | 15265 + firstname-15266 | lastname-15266 | 15266 + firstname-15267 | lastname-15267 | 15267 + firstname-15268 | lastname-15268 | 15268 + firstname-15269 | lastname-15269 | 15269 + firstname-15270 | lastname-15270 | 15270 + firstname-15271 | lastname-15271 | 15271 + firstname-15272 | lastname-15272 | 15272 + firstname-15273 | lastname-15273 | 15273 + firstname-15274 | lastname-15274 | 15274 + firstname-15275 | lastname-15275 | 15275 + firstname-15276 | lastname-15276 | 15276 + firstname-15277 | lastname-15277 | 15277 + firstname-15278 | lastname-15278 | 15278 + firstname-15279 | lastname-15279 | 15279 + firstname-15280 | lastname-15280 | 15280 + firstname-15281 | lastname-15281 | 15281 + firstname-15282 | lastname-15282 | 15282 + firstname-15283 | lastname-15283 | 15283 + firstname-15284 | lastname-15284 | 15284 + firstname-15285 | lastname-15285 | 15285 + firstname-15286 | lastname-15286 | 15286 + firstname-15287 | lastname-15287 | 15287 + firstname-15288 | lastname-15288 | 15288 + firstname-15289 | lastname-15289 | 15289 + firstname-15290 | lastname-15290 | 15290 + firstname-15291 | lastname-15291 | 15291 + firstname-15292 | lastname-15292 | 15292 + firstname-15293 | lastname-15293 | 15293 + firstname-15294 | lastname-15294 | 15294 + firstname-15295 | lastname-15295 | 15295 + firstname-15296 | lastname-15296 | 15296 + firstname-15297 | lastname-15297 | 15297 + firstname-15298 | lastname-15298 | 15298 + firstname-15299 | lastname-15299 | 15299 + firstname-153 | lastname-153 | 153 + firstname-15300 | lastname-15300 | 15300 + firstname-15301 | lastname-15301 | 15301 + firstname-15302 | lastname-15302 | 15302 + firstname-15303 | lastname-15303 | 15303 + firstname-15304 | lastname-15304 | 15304 + firstname-15305 | lastname-15305 | 15305 + firstname-15306 | lastname-15306 | 15306 + firstname-15307 | lastname-15307 | 15307 + firstname-15308 | lastname-15308 | 15308 + firstname-15309 | lastname-15309 | 15309 + firstname-15310 | lastname-15310 | 15310 + firstname-15311 | lastname-15311 | 15311 + firstname-15312 | lastname-15312 | 15312 + firstname-15313 | lastname-15313 | 15313 + firstname-15314 | lastname-15314 | 15314 + firstname-15315 | lastname-15315 | 15315 + firstname-15316 | lastname-15316 | 15316 + firstname-15317 | lastname-15317 | 15317 + firstname-15318 | lastname-15318 | 15318 + firstname-15319 | lastname-15319 | 15319 + firstname-15320 | lastname-15320 | 15320 + firstname-15321 | lastname-15321 | 15321 + firstname-15322 | lastname-15322 | 15322 + firstname-15323 | lastname-15323 | 15323 + firstname-15324 | lastname-15324 | 15324 + firstname-15325 | lastname-15325 | 15325 + firstname-15326 | lastname-15326 | 15326 + firstname-15327 | lastname-15327 | 15327 + firstname-15328 | lastname-15328 | 15328 + firstname-15329 | lastname-15329 | 15329 + firstname-15330 | lastname-15330 | 15330 + firstname-15331 | lastname-15331 | 15331 + firstname-15332 | lastname-15332 | 15332 + firstname-15333 | lastname-15333 | 15333 + firstname-15334 | lastname-15334 | 15334 + firstname-15335 | lastname-15335 | 15335 + firstname-15336 | lastname-15336 | 15336 + firstname-15337 | lastname-15337 | 15337 + firstname-15338 | lastname-15338 | 15338 + firstname-15339 | lastname-15339 | 15339 + firstname-15340 | lastname-15340 | 15340 + firstname-15341 | lastname-15341 | 15341 + firstname-15342 | lastname-15342 | 15342 + firstname-15343 | lastname-15343 | 15343 + firstname-15344 | lastname-15344 | 15344 + firstname-15345 | lastname-15345 | 15345 + firstname-15346 | lastname-15346 | 15346 + firstname-15347 | lastname-15347 | 15347 + firstname-15348 | lastname-15348 | 15348 + firstname-15349 | lastname-15349 | 15349 + firstname-15350 | lastname-15350 | 15350 + firstname-15351 | lastname-15351 | 15351 + firstname-15352 | lastname-15352 | 15352 + firstname-15353 | lastname-15353 | 15353 + firstname-15354 | lastname-15354 | 15354 + firstname-15355 | lastname-15355 | 15355 + firstname-15356 | lastname-15356 | 15356 + firstname-15357 | lastname-15357 | 15357 + firstname-15358 | lastname-15358 | 15358 + firstname-15359 | lastname-15359 | 15359 + firstname-15360 | lastname-15360 | 15360 + firstname-15361 | lastname-15361 | 15361 + firstname-15362 | lastname-15362 | 15362 + firstname-15363 | lastname-15363 | 15363 + firstname-15364 | lastname-15364 | 15364 + firstname-15365 | lastname-15365 | 15365 + firstname-15366 | lastname-15366 | 15366 + firstname-15367 | lastname-15367 | 15367 + firstname-15368 | lastname-15368 | 15368 + firstname-15369 | lastname-15369 | 15369 + firstname-15370 | lastname-15370 | 15370 + firstname-15371 | lastname-15371 | 15371 + firstname-15372 | lastname-15372 | 15372 + firstname-15373 | lastname-15373 | 15373 + firstname-15374 | lastname-15374 | 15374 + firstname-15375 | lastname-15375 | 15375 + firstname-15376 | lastname-15376 | 15376 + firstname-15377 | lastname-15377 | 15377 + firstname-15378 | lastname-15378 | 15378 + firstname-15379 | lastname-15379 | 15379 + firstname-15380 | lastname-15380 | 15380 + firstname-15381 | lastname-15381 | 15381 + firstname-15382 | lastname-15382 | 15382 + firstname-15383 | lastname-15383 | 15383 + firstname-15384 | lastname-15384 | 15384 + firstname-15385 | lastname-15385 | 15385 + firstname-15386 | lastname-15386 | 15386 + firstname-15387 | lastname-15387 | 15387 + firstname-15388 | lastname-15388 | 15388 + firstname-15389 | lastname-15389 | 15389 + firstname-15390 | lastname-15390 | 15390 + firstname-15391 | lastname-15391 | 15391 + firstname-15392 | lastname-15392 | 15392 + firstname-15393 | lastname-15393 | 15393 + firstname-15394 | lastname-15394 | 15394 + firstname-15395 | lastname-15395 | 15395 + firstname-15396 | lastname-15396 | 15396 + firstname-15397 | lastname-15397 | 15397 + firstname-15398 | lastname-15398 | 15398 + firstname-15399 | lastname-15399 | 15399 + firstname-154 | lastname-154 | 154 + firstname-15400 | lastname-15400 | 15400 + firstname-15401 | lastname-15401 | 15401 + firstname-15402 | lastname-15402 | 15402 + firstname-15403 | lastname-15403 | 15403 + firstname-15404 | lastname-15404 | 15404 + firstname-15405 | lastname-15405 | 15405 + firstname-15406 | lastname-15406 | 15406 + firstname-15407 | lastname-15407 | 15407 + firstname-15408 | lastname-15408 | 15408 + firstname-15409 | lastname-15409 | 15409 + firstname-15410 | lastname-15410 | 15410 + firstname-15411 | lastname-15411 | 15411 + firstname-15412 | lastname-15412 | 15412 + firstname-15413 | lastname-15413 | 15413 + firstname-15414 | lastname-15414 | 15414 + firstname-15415 | lastname-15415 | 15415 + firstname-15416 | lastname-15416 | 15416 + firstname-15417 | lastname-15417 | 15417 + firstname-15418 | lastname-15418 | 15418 + firstname-15419 | lastname-15419 | 15419 + firstname-15420 | lastname-15420 | 15420 + firstname-15421 | lastname-15421 | 15421 + firstname-15422 | lastname-15422 | 15422 + firstname-15423 | lastname-15423 | 15423 + firstname-15424 | lastname-15424 | 15424 + firstname-15425 | lastname-15425 | 15425 + firstname-15426 | lastname-15426 | 15426 + firstname-15427 | lastname-15427 | 15427 + firstname-15428 | lastname-15428 | 15428 + firstname-15429 | lastname-15429 | 15429 + firstname-15430 | lastname-15430 | 15430 + firstname-15431 | lastname-15431 | 15431 + firstname-15432 | lastname-15432 | 15432 + firstname-15433 | lastname-15433 | 15433 + firstname-15434 | lastname-15434 | 15434 + firstname-15435 | lastname-15435 | 15435 + firstname-15436 | lastname-15436 | 15436 + firstname-15437 | lastname-15437 | 15437 + firstname-15438 | lastname-15438 | 15438 + firstname-15439 | lastname-15439 | 15439 + firstname-15440 | lastname-15440 | 15440 + firstname-15441 | lastname-15441 | 15441 + firstname-15442 | lastname-15442 | 15442 + firstname-15443 | lastname-15443 | 15443 + firstname-15444 | lastname-15444 | 15444 + firstname-15445 | lastname-15445 | 15445 + firstname-15446 | lastname-15446 | 15446 + firstname-15447 | lastname-15447 | 15447 + firstname-15448 | lastname-15448 | 15448 + firstname-15449 | lastname-15449 | 15449 + firstname-15450 | lastname-15450 | 15450 + firstname-15451 | lastname-15451 | 15451 + firstname-15452 | lastname-15452 | 15452 + firstname-15453 | lastname-15453 | 15453 + firstname-15454 | lastname-15454 | 15454 + firstname-15455 | lastname-15455 | 15455 + firstname-15456 | lastname-15456 | 15456 + firstname-15457 | lastname-15457 | 15457 + firstname-15458 | lastname-15458 | 15458 + firstname-15459 | lastname-15459 | 15459 + firstname-15460 | lastname-15460 | 15460 + firstname-15461 | lastname-15461 | 15461 + firstname-15462 | lastname-15462 | 15462 + firstname-15463 | lastname-15463 | 15463 + firstname-15464 | lastname-15464 | 15464 + firstname-15465 | lastname-15465 | 15465 + firstname-15466 | lastname-15466 | 15466 + firstname-15467 | lastname-15467 | 15467 + firstname-15468 | lastname-15468 | 15468 + firstname-15469 | lastname-15469 | 15469 + firstname-15470 | lastname-15470 | 15470 + firstname-15471 | lastname-15471 | 15471 + firstname-15472 | lastname-15472 | 15472 + firstname-15473 | lastname-15473 | 15473 + firstname-15474 | lastname-15474 | 15474 + firstname-15475 | lastname-15475 | 15475 + firstname-15476 | lastname-15476 | 15476 + firstname-15477 | lastname-15477 | 15477 + firstname-15478 | lastname-15478 | 15478 + firstname-15479 | lastname-15479 | 15479 + firstname-15480 | lastname-15480 | 15480 + firstname-15481 | lastname-15481 | 15481 + firstname-15482 | lastname-15482 | 15482 + firstname-15483 | lastname-15483 | 15483 + firstname-15484 | lastname-15484 | 15484 + firstname-15485 | lastname-15485 | 15485 + firstname-15486 | lastname-15486 | 15486 + firstname-15487 | lastname-15487 | 15487 + firstname-15488 | lastname-15488 | 15488 + firstname-15489 | lastname-15489 | 15489 + firstname-15490 | lastname-15490 | 15490 + firstname-15491 | lastname-15491 | 15491 + firstname-15492 | lastname-15492 | 15492 + firstname-15493 | lastname-15493 | 15493 + firstname-15494 | lastname-15494 | 15494 + firstname-15495 | lastname-15495 | 15495 + firstname-15496 | lastname-15496 | 15496 + firstname-15497 | lastname-15497 | 15497 + firstname-15498 | lastname-15498 | 15498 + firstname-15499 | lastname-15499 | 15499 + firstname-155 | lastname-155 | 155 + firstname-15500 | lastname-15500 | 15500 + firstname-15501 | lastname-15501 | 15501 + firstname-15502 | lastname-15502 | 15502 + firstname-15503 | lastname-15503 | 15503 + firstname-15504 | lastname-15504 | 15504 + firstname-15505 | lastname-15505 | 15505 + firstname-15506 | lastname-15506 | 15506 + firstname-15507 | lastname-15507 | 15507 + firstname-15508 | lastname-15508 | 15508 + firstname-15509 | lastname-15509 | 15509 + firstname-15510 | lastname-15510 | 15510 + firstname-15511 | lastname-15511 | 15511 + firstname-15512 | lastname-15512 | 15512 + firstname-15513 | lastname-15513 | 15513 + firstname-15514 | lastname-15514 | 15514 + firstname-15515 | lastname-15515 | 15515 + firstname-15516 | lastname-15516 | 15516 + firstname-15517 | lastname-15517 | 15517 + firstname-15518 | lastname-15518 | 15518 + firstname-15519 | lastname-15519 | 15519 + firstname-15520 | lastname-15520 | 15520 + firstname-15521 | lastname-15521 | 15521 + firstname-15522 | lastname-15522 | 15522 + firstname-15523 | lastname-15523 | 15523 + firstname-15524 | lastname-15524 | 15524 + firstname-15525 | lastname-15525 | 15525 + firstname-15526 | lastname-15526 | 15526 + firstname-15527 | lastname-15527 | 15527 + firstname-15528 | lastname-15528 | 15528 + firstname-15529 | lastname-15529 | 15529 + firstname-15530 | lastname-15530 | 15530 + firstname-15531 | lastname-15531 | 15531 + firstname-15532 | lastname-15532 | 15532 + firstname-15533 | lastname-15533 | 15533 + firstname-15534 | lastname-15534 | 15534 + firstname-15535 | lastname-15535 | 15535 + firstname-15536 | lastname-15536 | 15536 + firstname-15537 | lastname-15537 | 15537 + firstname-15538 | lastname-15538 | 15538 + firstname-15539 | lastname-15539 | 15539 + firstname-15540 | lastname-15540 | 15540 + firstname-15541 | lastname-15541 | 15541 + firstname-15542 | lastname-15542 | 15542 + firstname-15543 | lastname-15543 | 15543 + firstname-15544 | lastname-15544 | 15544 + firstname-15545 | lastname-15545 | 15545 + firstname-15546 | lastname-15546 | 15546 + firstname-15547 | lastname-15547 | 15547 + firstname-15548 | lastname-15548 | 15548 + firstname-15549 | lastname-15549 | 15549 + firstname-15550 | lastname-15550 | 15550 + firstname-15551 | lastname-15551 | 15551 + firstname-15552 | lastname-15552 | 15552 + firstname-15553 | lastname-15553 | 15553 + firstname-15554 | lastname-15554 | 15554 + firstname-15555 | lastname-15555 | 15555 + firstname-15556 | lastname-15556 | 15556 + firstname-15557 | lastname-15557 | 15557 + firstname-15558 | lastname-15558 | 15558 + firstname-15559 | lastname-15559 | 15559 + firstname-15560 | lastname-15560 | 15560 + firstname-15561 | lastname-15561 | 15561 + firstname-15562 | lastname-15562 | 15562 + firstname-15563 | lastname-15563 | 15563 + firstname-15564 | lastname-15564 | 15564 + firstname-15565 | lastname-15565 | 15565 + firstname-15566 | lastname-15566 | 15566 + firstname-15567 | lastname-15567 | 15567 + firstname-15568 | lastname-15568 | 15568 + firstname-15569 | lastname-15569 | 15569 + firstname-15570 | lastname-15570 | 15570 + firstname-15571 | lastname-15571 | 15571 + firstname-15572 | lastname-15572 | 15572 + firstname-15573 | lastname-15573 | 15573 + firstname-15574 | lastname-15574 | 15574 + firstname-15575 | lastname-15575 | 15575 + firstname-15576 | lastname-15576 | 15576 + firstname-15577 | lastname-15577 | 15577 + firstname-15578 | lastname-15578 | 15578 + firstname-15579 | lastname-15579 | 15579 + firstname-15580 | lastname-15580 | 15580 + firstname-15581 | lastname-15581 | 15581 + firstname-15582 | lastname-15582 | 15582 + firstname-15583 | lastname-15583 | 15583 + firstname-15584 | lastname-15584 | 15584 + firstname-15585 | lastname-15585 | 15585 + firstname-15586 | lastname-15586 | 15586 + firstname-15587 | lastname-15587 | 15587 + firstname-15588 | lastname-15588 | 15588 + firstname-15589 | lastname-15589 | 15589 + firstname-15590 | lastname-15590 | 15590 + firstname-15591 | lastname-15591 | 15591 + firstname-15592 | lastname-15592 | 15592 + firstname-15593 | lastname-15593 | 15593 + firstname-15594 | lastname-15594 | 15594 + firstname-15595 | lastname-15595 | 15595 + firstname-15596 | lastname-15596 | 15596 + firstname-15597 | lastname-15597 | 15597 + firstname-15598 | lastname-15598 | 15598 + firstname-15599 | lastname-15599 | 15599 + firstname-156 | lastname-156 | 156 + firstname-15600 | lastname-15600 | 15600 + firstname-15601 | lastname-15601 | 15601 + firstname-15602 | lastname-15602 | 15602 + firstname-15603 | lastname-15603 | 15603 + firstname-15604 | lastname-15604 | 15604 + firstname-15605 | lastname-15605 | 15605 + firstname-15606 | lastname-15606 | 15606 + firstname-15607 | lastname-15607 | 15607 + firstname-15608 | lastname-15608 | 15608 + firstname-15609 | lastname-15609 | 15609 + firstname-15610 | lastname-15610 | 15610 + firstname-15611 | lastname-15611 | 15611 + firstname-15612 | lastname-15612 | 15612 + firstname-15613 | lastname-15613 | 15613 + firstname-15614 | lastname-15614 | 15614 + firstname-15615 | lastname-15615 | 15615 + firstname-15616 | lastname-15616 | 15616 + firstname-15617 | lastname-15617 | 15617 + firstname-15618 | lastname-15618 | 15618 + firstname-15619 | lastname-15619 | 15619 + firstname-15620 | lastname-15620 | 15620 + firstname-15621 | lastname-15621 | 15621 + firstname-15622 | lastname-15622 | 15622 + firstname-15623 | lastname-15623 | 15623 + firstname-15624 | lastname-15624 | 15624 + firstname-15625 | lastname-15625 | 15625 + firstname-15626 | lastname-15626 | 15626 + firstname-15627 | lastname-15627 | 15627 + firstname-15628 | lastname-15628 | 15628 + firstname-15629 | lastname-15629 | 15629 + firstname-15630 | lastname-15630 | 15630 + firstname-15631 | lastname-15631 | 15631 + firstname-15632 | lastname-15632 | 15632 + firstname-15633 | lastname-15633 | 15633 + firstname-15634 | lastname-15634 | 15634 + firstname-15635 | lastname-15635 | 15635 + firstname-15636 | lastname-15636 | 15636 + firstname-15637 | lastname-15637 | 15637 + firstname-15638 | lastname-15638 | 15638 + firstname-15639 | lastname-15639 | 15639 + firstname-15640 | lastname-15640 | 15640 + firstname-15641 | lastname-15641 | 15641 + firstname-15642 | lastname-15642 | 15642 + firstname-15643 | lastname-15643 | 15643 + firstname-15644 | lastname-15644 | 15644 + firstname-15645 | lastname-15645 | 15645 + firstname-15646 | lastname-15646 | 15646 + firstname-15647 | lastname-15647 | 15647 + firstname-15648 | lastname-15648 | 15648 + firstname-15649 | lastname-15649 | 15649 + firstname-15650 | lastname-15650 | 15650 + firstname-15651 | lastname-15651 | 15651 + firstname-15652 | lastname-15652 | 15652 + firstname-15653 | lastname-15653 | 15653 + firstname-15654 | lastname-15654 | 15654 + firstname-15655 | lastname-15655 | 15655 + firstname-15656 | lastname-15656 | 15656 + firstname-15657 | lastname-15657 | 15657 + firstname-15658 | lastname-15658 | 15658 + firstname-15659 | lastname-15659 | 15659 + firstname-15660 | lastname-15660 | 15660 + firstname-15661 | lastname-15661 | 15661 + firstname-15662 | lastname-15662 | 15662 + firstname-15663 | lastname-15663 | 15663 + firstname-15664 | lastname-15664 | 15664 + firstname-15665 | lastname-15665 | 15665 + firstname-15666 | lastname-15666 | 15666 + firstname-15667 | lastname-15667 | 15667 + firstname-15668 | lastname-15668 | 15668 + firstname-15669 | lastname-15669 | 15669 + firstname-15670 | lastname-15670 | 15670 + firstname-15671 | lastname-15671 | 15671 + firstname-15672 | lastname-15672 | 15672 + firstname-15673 | lastname-15673 | 15673 + firstname-15674 | lastname-15674 | 15674 + firstname-15675 | lastname-15675 | 15675 + firstname-15676 | lastname-15676 | 15676 + firstname-15677 | lastname-15677 | 15677 + firstname-15678 | lastname-15678 | 15678 + firstname-15679 | lastname-15679 | 15679 + firstname-15680 | lastname-15680 | 15680 + firstname-15681 | lastname-15681 | 15681 + firstname-15682 | lastname-15682 | 15682 + firstname-15683 | lastname-15683 | 15683 + firstname-15684 | lastname-15684 | 15684 + firstname-15685 | lastname-15685 | 15685 + firstname-15686 | lastname-15686 | 15686 + firstname-15687 | lastname-15687 | 15687 + firstname-15688 | lastname-15688 | 15688 + firstname-15689 | lastname-15689 | 15689 + firstname-15690 | lastname-15690 | 15690 + firstname-15691 | lastname-15691 | 15691 + firstname-15692 | lastname-15692 | 15692 + firstname-15693 | lastname-15693 | 15693 + firstname-15694 | lastname-15694 | 15694 + firstname-15695 | lastname-15695 | 15695 + firstname-15696 | lastname-15696 | 15696 + firstname-15697 | lastname-15697 | 15697 + firstname-15698 | lastname-15698 | 15698 + firstname-15699 | lastname-15699 | 15699 + firstname-157 | lastname-157 | 157 + firstname-15700 | lastname-15700 | 15700 + firstname-15701 | lastname-15701 | 15701 + firstname-15702 | lastname-15702 | 15702 + firstname-15703 | lastname-15703 | 15703 + firstname-15704 | lastname-15704 | 15704 + firstname-15705 | lastname-15705 | 15705 + firstname-15706 | lastname-15706 | 15706 + firstname-15707 | lastname-15707 | 15707 + firstname-15708 | lastname-15708 | 15708 + firstname-15709 | lastname-15709 | 15709 + firstname-15710 | lastname-15710 | 15710 + firstname-15711 | lastname-15711 | 15711 + firstname-15712 | lastname-15712 | 15712 + firstname-15713 | lastname-15713 | 15713 + firstname-15714 | lastname-15714 | 15714 + firstname-15715 | lastname-15715 | 15715 + firstname-15716 | lastname-15716 | 15716 + firstname-15717 | lastname-15717 | 15717 + firstname-15718 | lastname-15718 | 15718 + firstname-15719 | lastname-15719 | 15719 + firstname-15720 | lastname-15720 | 15720 + firstname-15721 | lastname-15721 | 15721 + firstname-15722 | lastname-15722 | 15722 + firstname-15723 | lastname-15723 | 15723 + firstname-15724 | lastname-15724 | 15724 + firstname-15725 | lastname-15725 | 15725 + firstname-15726 | lastname-15726 | 15726 + firstname-15727 | lastname-15727 | 15727 + firstname-15728 | lastname-15728 | 15728 + firstname-15729 | lastname-15729 | 15729 + firstname-15730 | lastname-15730 | 15730 + firstname-15731 | lastname-15731 | 15731 + firstname-15732 | lastname-15732 | 15732 + firstname-15733 | lastname-15733 | 15733 + firstname-15734 | lastname-15734 | 15734 + firstname-15735 | lastname-15735 | 15735 + firstname-15736 | lastname-15736 | 15736 + firstname-15737 | lastname-15737 | 15737 + firstname-15738 | lastname-15738 | 15738 + firstname-15739 | lastname-15739 | 15739 + firstname-15740 | lastname-15740 | 15740 + firstname-15741 | lastname-15741 | 15741 + firstname-15742 | lastname-15742 | 15742 + firstname-15743 | lastname-15743 | 15743 + firstname-15744 | lastname-15744 | 15744 + firstname-15745 | lastname-15745 | 15745 + firstname-15746 | lastname-15746 | 15746 + firstname-15747 | lastname-15747 | 15747 + firstname-15748 | lastname-15748 | 15748 + firstname-15749 | lastname-15749 | 15749 + firstname-15750 | lastname-15750 | 15750 + firstname-15751 | lastname-15751 | 15751 + firstname-15752 | lastname-15752 | 15752 + firstname-15753 | lastname-15753 | 15753 + firstname-15754 | lastname-15754 | 15754 + firstname-15755 | lastname-15755 | 15755 + firstname-15756 | lastname-15756 | 15756 + firstname-15757 | lastname-15757 | 15757 + firstname-15758 | lastname-15758 | 15758 + firstname-15759 | lastname-15759 | 15759 + firstname-15760 | lastname-15760 | 15760 + firstname-15761 | lastname-15761 | 15761 + firstname-15762 | lastname-15762 | 15762 + firstname-15763 | lastname-15763 | 15763 + firstname-15764 | lastname-15764 | 15764 + firstname-15765 | lastname-15765 | 15765 + firstname-15766 | lastname-15766 | 15766 + firstname-15767 | lastname-15767 | 15767 + firstname-15768 | lastname-15768 | 15768 + firstname-15769 | lastname-15769 | 15769 + firstname-15770 | lastname-15770 | 15770 + firstname-15771 | lastname-15771 | 15771 + firstname-15772 | lastname-15772 | 15772 + firstname-15773 | lastname-15773 | 15773 + firstname-15774 | lastname-15774 | 15774 + firstname-15775 | lastname-15775 | 15775 + firstname-15776 | lastname-15776 | 15776 + firstname-15777 | lastname-15777 | 15777 + firstname-15778 | lastname-15778 | 15778 + firstname-15779 | lastname-15779 | 15779 + firstname-15780 | lastname-15780 | 15780 + firstname-15781 | lastname-15781 | 15781 + firstname-15782 | lastname-15782 | 15782 + firstname-15783 | lastname-15783 | 15783 + firstname-15784 | lastname-15784 | 15784 + firstname-15785 | lastname-15785 | 15785 + firstname-15786 | lastname-15786 | 15786 + firstname-15787 | lastname-15787 | 15787 + firstname-15788 | lastname-15788 | 15788 + firstname-15789 | lastname-15789 | 15789 + firstname-15790 | lastname-15790 | 15790 + firstname-15791 | lastname-15791 | 15791 + firstname-15792 | lastname-15792 | 15792 + firstname-15793 | lastname-15793 | 15793 + firstname-15794 | lastname-15794 | 15794 + firstname-15795 | lastname-15795 | 15795 + firstname-15796 | lastname-15796 | 15796 + firstname-15797 | lastname-15797 | 15797 + firstname-15798 | lastname-15798 | 15798 + firstname-15799 | lastname-15799 | 15799 + firstname-158 | lastname-158 | 158 + firstname-15800 | lastname-15800 | 15800 + firstname-15801 | lastname-15801 | 15801 + firstname-15802 | lastname-15802 | 15802 + firstname-15803 | lastname-15803 | 15803 + firstname-15804 | lastname-15804 | 15804 + firstname-15805 | lastname-15805 | 15805 + firstname-15806 | lastname-15806 | 15806 + firstname-15807 | lastname-15807 | 15807 + firstname-15808 | lastname-15808 | 15808 + firstname-15809 | lastname-15809 | 15809 + firstname-15810 | lastname-15810 | 15810 + firstname-15811 | lastname-15811 | 15811 + firstname-15812 | lastname-15812 | 15812 + firstname-15813 | lastname-15813 | 15813 + firstname-15814 | lastname-15814 | 15814 + firstname-15815 | lastname-15815 | 15815 + firstname-15816 | lastname-15816 | 15816 + firstname-15817 | lastname-15817 | 15817 + firstname-15818 | lastname-15818 | 15818 + firstname-15819 | lastname-15819 | 15819 + firstname-15820 | lastname-15820 | 15820 + firstname-15821 | lastname-15821 | 15821 + firstname-15822 | lastname-15822 | 15822 + firstname-15823 | lastname-15823 | 15823 + firstname-15824 | lastname-15824 | 15824 + firstname-15825 | lastname-15825 | 15825 + firstname-15826 | lastname-15826 | 15826 + firstname-15827 | lastname-15827 | 15827 + firstname-15828 | lastname-15828 | 15828 + firstname-15829 | lastname-15829 | 15829 + firstname-15830 | lastname-15830 | 15830 + firstname-15831 | lastname-15831 | 15831 + firstname-15832 | lastname-15832 | 15832 + firstname-15833 | lastname-15833 | 15833 + firstname-15834 | lastname-15834 | 15834 + firstname-15835 | lastname-15835 | 15835 + firstname-15836 | lastname-15836 | 15836 + firstname-15837 | lastname-15837 | 15837 + firstname-15838 | lastname-15838 | 15838 + firstname-15839 | lastname-15839 | 15839 + firstname-15840 | lastname-15840 | 15840 + firstname-15841 | lastname-15841 | 15841 + firstname-15842 | lastname-15842 | 15842 + firstname-15843 | lastname-15843 | 15843 + firstname-15844 | lastname-15844 | 15844 + firstname-15845 | lastname-15845 | 15845 + firstname-15846 | lastname-15846 | 15846 + firstname-15847 | lastname-15847 | 15847 + firstname-15848 | lastname-15848 | 15848 + firstname-15849 | lastname-15849 | 15849 + firstname-15850 | lastname-15850 | 15850 + firstname-15851 | lastname-15851 | 15851 + firstname-15852 | lastname-15852 | 15852 + firstname-15853 | lastname-15853 | 15853 + firstname-15854 | lastname-15854 | 15854 + firstname-15855 | lastname-15855 | 15855 + firstname-15856 | lastname-15856 | 15856 + firstname-15857 | lastname-15857 | 15857 + firstname-15858 | lastname-15858 | 15858 + firstname-15859 | lastname-15859 | 15859 + firstname-15860 | lastname-15860 | 15860 + firstname-15861 | lastname-15861 | 15861 + firstname-15862 | lastname-15862 | 15862 + firstname-15863 | lastname-15863 | 15863 + firstname-15864 | lastname-15864 | 15864 + firstname-15865 | lastname-15865 | 15865 + firstname-15866 | lastname-15866 | 15866 + firstname-15867 | lastname-15867 | 15867 + firstname-15868 | lastname-15868 | 15868 + firstname-15869 | lastname-15869 | 15869 + firstname-15870 | lastname-15870 | 15870 + firstname-15871 | lastname-15871 | 15871 + firstname-15872 | lastname-15872 | 15872 + firstname-15873 | lastname-15873 | 15873 + firstname-15874 | lastname-15874 | 15874 + firstname-15875 | lastname-15875 | 15875 + firstname-15876 | lastname-15876 | 15876 + firstname-15877 | lastname-15877 | 15877 + firstname-15878 | lastname-15878 | 15878 + firstname-15879 | lastname-15879 | 15879 + firstname-15880 | lastname-15880 | 15880 + firstname-15881 | lastname-15881 | 15881 + firstname-15882 | lastname-15882 | 15882 + firstname-15883 | lastname-15883 | 15883 + firstname-15884 | lastname-15884 | 15884 + firstname-15885 | lastname-15885 | 15885 + firstname-15886 | lastname-15886 | 15886 + firstname-15887 | lastname-15887 | 15887 + firstname-15888 | lastname-15888 | 15888 + firstname-15889 | lastname-15889 | 15889 + firstname-15890 | lastname-15890 | 15890 + firstname-15891 | lastname-15891 | 15891 + firstname-15892 | lastname-15892 | 15892 + firstname-15893 | lastname-15893 | 15893 + firstname-15894 | lastname-15894 | 15894 + firstname-15895 | lastname-15895 | 15895 + firstname-15896 | lastname-15896 | 15896 + firstname-15897 | lastname-15897 | 15897 + firstname-15898 | lastname-15898 | 15898 + firstname-15899 | lastname-15899 | 15899 + firstname-159 | lastname-159 | 159 + firstname-15900 | lastname-15900 | 15900 + firstname-15901 | lastname-15901 | 15901 + firstname-15902 | lastname-15902 | 15902 + firstname-15903 | lastname-15903 | 15903 + firstname-15904 | lastname-15904 | 15904 + firstname-15905 | lastname-15905 | 15905 + firstname-15906 | lastname-15906 | 15906 + firstname-15907 | lastname-15907 | 15907 + firstname-15908 | lastname-15908 | 15908 + firstname-15909 | lastname-15909 | 15909 + firstname-15910 | lastname-15910 | 15910 + firstname-15911 | lastname-15911 | 15911 + firstname-15912 | lastname-15912 | 15912 + firstname-15913 | lastname-15913 | 15913 + firstname-15914 | lastname-15914 | 15914 + firstname-15915 | lastname-15915 | 15915 + firstname-15916 | lastname-15916 | 15916 + firstname-15917 | lastname-15917 | 15917 + firstname-15918 | lastname-15918 | 15918 + firstname-15919 | lastname-15919 | 15919 + firstname-15920 | lastname-15920 | 15920 + firstname-15921 | lastname-15921 | 15921 + firstname-15922 | lastname-15922 | 15922 + firstname-15923 | lastname-15923 | 15923 + firstname-15924 | lastname-15924 | 15924 + firstname-15925 | lastname-15925 | 15925 + firstname-15926 | lastname-15926 | 15926 + firstname-15927 | lastname-15927 | 15927 + firstname-15928 | lastname-15928 | 15928 + firstname-15929 | lastname-15929 | 15929 + firstname-15930 | lastname-15930 | 15930 + firstname-15931 | lastname-15931 | 15931 + firstname-15932 | lastname-15932 | 15932 + firstname-15933 | lastname-15933 | 15933 + firstname-15934 | lastname-15934 | 15934 + firstname-15935 | lastname-15935 | 15935 + firstname-15936 | lastname-15936 | 15936 + firstname-15937 | lastname-15937 | 15937 + firstname-15938 | lastname-15938 | 15938 + firstname-15939 | lastname-15939 | 15939 + firstname-15940 | lastname-15940 | 15940 + firstname-15941 | lastname-15941 | 15941 + firstname-15942 | lastname-15942 | 15942 + firstname-15943 | lastname-15943 | 15943 + firstname-15944 | lastname-15944 | 15944 + firstname-15945 | lastname-15945 | 15945 + firstname-15946 | lastname-15946 | 15946 + firstname-15947 | lastname-15947 | 15947 + firstname-15948 | lastname-15948 | 15948 + firstname-15949 | lastname-15949 | 15949 + firstname-15950 | lastname-15950 | 15950 + firstname-15951 | lastname-15951 | 15951 + firstname-15952 | lastname-15952 | 15952 + firstname-15953 | lastname-15953 | 15953 + firstname-15954 | lastname-15954 | 15954 + firstname-15955 | lastname-15955 | 15955 + firstname-15956 | lastname-15956 | 15956 + firstname-15957 | lastname-15957 | 15957 + firstname-15958 | lastname-15958 | 15958 + firstname-15959 | lastname-15959 | 15959 + firstname-15960 | lastname-15960 | 15960 + firstname-15961 | lastname-15961 | 15961 + firstname-15962 | lastname-15962 | 15962 + firstname-15963 | lastname-15963 | 15963 + firstname-15964 | lastname-15964 | 15964 + firstname-15965 | lastname-15965 | 15965 + firstname-15966 | lastname-15966 | 15966 + firstname-15967 | lastname-15967 | 15967 + firstname-15968 | lastname-15968 | 15968 + firstname-15969 | lastname-15969 | 15969 + firstname-15970 | lastname-15970 | 15970 + firstname-15971 | lastname-15971 | 15971 + firstname-15972 | lastname-15972 | 15972 + firstname-15973 | lastname-15973 | 15973 + firstname-15974 | lastname-15974 | 15974 + firstname-15975 | lastname-15975 | 15975 + firstname-15976 | lastname-15976 | 15976 + firstname-15977 | lastname-15977 | 15977 + firstname-15978 | lastname-15978 | 15978 + firstname-15979 | lastname-15979 | 15979 + firstname-15980 | lastname-15980 | 15980 + firstname-15981 | lastname-15981 | 15981 + firstname-15982 | lastname-15982 | 15982 + firstname-15983 | lastname-15983 | 15983 + firstname-15984 | lastname-15984 | 15984 + firstname-15985 | lastname-15985 | 15985 + firstname-15986 | lastname-15986 | 15986 + firstname-15987 | lastname-15987 | 15987 + firstname-15988 | lastname-15988 | 15988 + firstname-15989 | lastname-15989 | 15989 + firstname-15990 | lastname-15990 | 15990 + firstname-15991 | lastname-15991 | 15991 + firstname-15992 | lastname-15992 | 15992 + firstname-15993 | lastname-15993 | 15993 + firstname-15994 | lastname-15994 | 15994 + firstname-15995 | lastname-15995 | 15995 + firstname-15996 | lastname-15996 | 15996 + firstname-15997 | lastname-15997 | 15997 + firstname-15998 | lastname-15998 | 15998 + firstname-15999 | lastname-15999 | 15999 + firstname-16 | lastname-16 | 16 + firstname-160 | lastname-160 | 160 + firstname-16000 | lastname-16000 | 16000 + firstname-161 | lastname-161 | 161 + firstname-162 | lastname-162 | 162 + firstname-163 | lastname-163 | 163 + firstname-164 | lastname-164 | 164 + firstname-165 | lastname-165 | 165 + firstname-166 | lastname-166 | 166 + firstname-167 | lastname-167 | 167 + firstname-168 | lastname-168 | 168 + firstname-169 | lastname-169 | 169 + firstname-17 | lastname-17 | 17 + firstname-170 | lastname-170 | 170 + firstname-171 | lastname-171 | 171 + firstname-172 | lastname-172 | 172 + firstname-173 | lastname-173 | 173 + firstname-174 | lastname-174 | 174 + firstname-175 | lastname-175 | 175 + firstname-176 | lastname-176 | 176 + firstname-177 | lastname-177 | 177 + firstname-178 | lastname-178 | 178 + firstname-179 | lastname-179 | 179 + firstname-18 | lastname-18 | 18 + firstname-180 | lastname-180 | 180 + firstname-181 | lastname-181 | 181 + firstname-182 | lastname-182 | 182 + firstname-183 | lastname-183 | 183 + firstname-184 | lastname-184 | 184 + firstname-185 | lastname-185 | 185 + firstname-186 | lastname-186 | 186 + firstname-187 | lastname-187 | 187 + firstname-188 | lastname-188 | 188 + firstname-189 | lastname-189 | 189 + firstname-19 | lastname-19 | 19 + firstname-190 | lastname-190 | 190 + firstname-191 | lastname-191 | 191 + firstname-192 | lastname-192 | 192 + firstname-193 | lastname-193 | 193 + firstname-194 | lastname-194 | 194 + firstname-195 | lastname-195 | 195 + firstname-196 | lastname-196 | 196 + firstname-197 | lastname-197 | 197 + firstname-198 | lastname-198 | 198 + firstname-199 | lastname-199 | 199 + firstname-2 | lastname-2 | 2 + firstname-20 | lastname-20 | 20 + firstname-200 | lastname-200 | 200 + firstname-201 | lastname-201 | 201 + firstname-202 | lastname-202 | 202 + firstname-203 | lastname-203 | 203 + firstname-204 | lastname-204 | 204 + firstname-205 | lastname-205 | 205 + firstname-206 | lastname-206 | 206 + firstname-207 | lastname-207 | 207 + firstname-208 | lastname-208 | 208 + firstname-209 | lastname-209 | 209 + firstname-21 | lastname-21 | 21 + firstname-210 | lastname-210 | 210 + firstname-211 | lastname-211 | 211 + firstname-212 | lastname-212 | 212 + firstname-213 | lastname-213 | 213 + firstname-214 | lastname-214 | 214 + firstname-215 | lastname-215 | 215 + firstname-216 | lastname-216 | 216 + firstname-217 | lastname-217 | 217 + firstname-218 | lastname-218 | 218 + firstname-219 | lastname-219 | 219 + firstname-22 | lastname-22 | 22 + firstname-220 | lastname-220 | 220 + firstname-221 | lastname-221 | 221 + firstname-222 | lastname-222 | 222 + firstname-223 | lastname-223 | 223 + firstname-224 | lastname-224 | 224 + firstname-225 | lastname-225 | 225 + firstname-226 | lastname-226 | 226 + firstname-227 | lastname-227 | 227 + firstname-228 | lastname-228 | 228 + firstname-229 | lastname-229 | 229 + firstname-23 | lastname-23 | 23 + firstname-230 | lastname-230 | 230 + firstname-231 | lastname-231 | 231 + firstname-232 | lastname-232 | 232 + firstname-233 | lastname-233 | 233 + firstname-234 | lastname-234 | 234 + firstname-235 | lastname-235 | 235 + firstname-236 | lastname-236 | 236 + firstname-237 | lastname-237 | 237 + firstname-238 | lastname-238 | 238 + firstname-239 | lastname-239 | 239 + firstname-24 | lastname-24 | 24 + firstname-240 | lastname-240 | 240 + firstname-241 | lastname-241 | 241 + firstname-242 | lastname-242 | 242 + firstname-243 | lastname-243 | 243 + firstname-244 | lastname-244 | 244 + firstname-245 | lastname-245 | 245 + firstname-246 | lastname-246 | 246 + firstname-247 | lastname-247 | 247 + firstname-248 | lastname-248 | 248 + firstname-249 | lastname-249 | 249 + firstname-25 | lastname-25 | 25 + firstname-250 | lastname-250 | 250 + firstname-251 | lastname-251 | 251 + firstname-252 | lastname-252 | 252 + firstname-253 | lastname-253 | 253 + firstname-254 | lastname-254 | 254 + firstname-255 | lastname-255 | 255 + firstname-256 | lastname-256 | 256 + firstname-257 | lastname-257 | 257 + firstname-258 | lastname-258 | 258 + firstname-259 | lastname-259 | 259 + firstname-26 | lastname-26 | 26 + firstname-260 | lastname-260 | 260 + firstname-261 | lastname-261 | 261 + firstname-262 | lastname-262 | 262 + firstname-263 | lastname-263 | 263 + firstname-264 | lastname-264 | 264 + firstname-265 | lastname-265 | 265 + firstname-266 | lastname-266 | 266 + firstname-267 | lastname-267 | 267 + firstname-268 | lastname-268 | 268 + firstname-269 | lastname-269 | 269 + firstname-27 | lastname-27 | 27 + firstname-270 | lastname-270 | 270 + firstname-271 | lastname-271 | 271 + firstname-272 | lastname-272 | 272 + firstname-273 | lastname-273 | 273 + firstname-274 | lastname-274 | 274 + firstname-275 | lastname-275 | 275 + firstname-276 | lastname-276 | 276 + firstname-277 | lastname-277 | 277 + firstname-278 | lastname-278 | 278 + firstname-279 | lastname-279 | 279 + firstname-28 | lastname-28 | 28 + firstname-280 | lastname-280 | 280 + firstname-281 | lastname-281 | 281 + firstname-282 | lastname-282 | 282 + firstname-283 | lastname-283 | 283 + firstname-284 | lastname-284 | 284 + firstname-285 | lastname-285 | 285 + firstname-286 | lastname-286 | 286 + firstname-287 | lastname-287 | 287 + firstname-288 | lastname-288 | 288 + firstname-289 | lastname-289 | 289 + firstname-29 | lastname-29 | 29 + firstname-290 | lastname-290 | 290 + firstname-291 | lastname-291 | 291 + firstname-292 | lastname-292 | 292 + firstname-293 | lastname-293 | 293 + firstname-294 | lastname-294 | 294 + firstname-295 | lastname-295 | 295 + firstname-296 | lastname-296 | 296 + firstname-297 | lastname-297 | 297 + firstname-298 | lastname-298 | 298 + firstname-299 | lastname-299 | 299 + firstname-3 | lastname-3 | 3 + firstname-30 | lastname-30 | 30 + firstname-300 | lastname-300 | 300 + firstname-301 | lastname-301 | 301 + firstname-302 | lastname-302 | 302 + firstname-303 | lastname-303 | 303 + firstname-304 | lastname-304 | 304 + firstname-305 | lastname-305 | 305 + firstname-306 | lastname-306 | 306 + firstname-307 | lastname-307 | 307 + firstname-308 | lastname-308 | 308 + firstname-309 | lastname-309 | 309 + firstname-31 | lastname-31 | 31 + firstname-310 | lastname-310 | 310 + firstname-311 | lastname-311 | 311 + firstname-312 | lastname-312 | 312 + firstname-313 | lastname-313 | 313 + firstname-314 | lastname-314 | 314 + firstname-315 | lastname-315 | 315 + firstname-316 | lastname-316 | 316 + firstname-317 | lastname-317 | 317 + firstname-318 | lastname-318 | 318 + firstname-319 | lastname-319 | 319 + firstname-32 | lastname-32 | 32 + firstname-320 | lastname-320 | 320 + firstname-321 | lastname-321 | 321 + firstname-322 | lastname-322 | 322 + firstname-323 | lastname-323 | 323 + firstname-324 | lastname-324 | 324 + firstname-325 | lastname-325 | 325 + firstname-326 | lastname-326 | 326 + firstname-327 | lastname-327 | 327 + firstname-328 | lastname-328 | 328 + firstname-329 | lastname-329 | 329 + firstname-33 | lastname-33 | 33 + firstname-330 | lastname-330 | 330 + firstname-331 | lastname-331 | 331 + firstname-332 | lastname-332 | 332 + firstname-333 | lastname-333 | 333 + firstname-334 | lastname-334 | 334 + firstname-335 | lastname-335 | 335 + firstname-336 | lastname-336 | 336 + firstname-337 | lastname-337 | 337 + firstname-338 | lastname-338 | 338 + firstname-339 | lastname-339 | 339 + firstname-34 | lastname-34 | 34 + firstname-340 | lastname-340 | 340 + firstname-341 | lastname-341 | 341 + firstname-342 | lastname-342 | 342 + firstname-343 | lastname-343 | 343 + firstname-344 | lastname-344 | 344 + firstname-345 | lastname-345 | 345 + firstname-346 | lastname-346 | 346 + firstname-347 | lastname-347 | 347 + firstname-348 | lastname-348 | 348 + firstname-349 | lastname-349 | 349 + firstname-35 | lastname-35 | 35 + firstname-350 | lastname-350 | 350 + firstname-351 | lastname-351 | 351 + firstname-352 | lastname-352 | 352 + firstname-353 | lastname-353 | 353 + firstname-354 | lastname-354 | 354 + firstname-355 | lastname-355 | 355 + firstname-356 | lastname-356 | 356 + firstname-357 | lastname-357 | 357 + firstname-358 | lastname-358 | 358 + firstname-359 | lastname-359 | 359 + firstname-36 | lastname-36 | 36 + firstname-360 | lastname-360 | 360 + firstname-361 | lastname-361 | 361 + firstname-362 | lastname-362 | 362 + firstname-363 | lastname-363 | 363 + firstname-364 | lastname-364 | 364 + firstname-365 | lastname-365 | 365 + firstname-366 | lastname-366 | 366 + firstname-367 | lastname-367 | 367 + firstname-368 | lastname-368 | 368 + firstname-369 | lastname-369 | 369 + firstname-37 | lastname-37 | 37 + firstname-370 | lastname-370 | 370 + firstname-371 | lastname-371 | 371 + firstname-372 | lastname-372 | 372 + firstname-373 | lastname-373 | 373 + firstname-374 | lastname-374 | 374 + firstname-375 | lastname-375 | 375 + firstname-376 | lastname-376 | 376 + firstname-377 | lastname-377 | 377 + firstname-378 | lastname-378 | 378 + firstname-379 | lastname-379 | 379 + firstname-38 | lastname-38 | 38 + firstname-380 | lastname-380 | 380 + firstname-381 | lastname-381 | 381 + firstname-382 | lastname-382 | 382 + firstname-383 | lastname-383 | 383 + firstname-384 | lastname-384 | 384 + firstname-385 | lastname-385 | 385 + firstname-386 | lastname-386 | 386 + firstname-387 | lastname-387 | 387 + firstname-388 | lastname-388 | 388 + firstname-389 | lastname-389 | 389 + firstname-39 | lastname-39 | 39 + firstname-390 | lastname-390 | 390 + firstname-391 | lastname-391 | 391 + firstname-392 | lastname-392 | 392 + firstname-393 | lastname-393 | 393 + firstname-394 | lastname-394 | 394 + firstname-395 | lastname-395 | 395 + firstname-396 | lastname-396 | 396 + firstname-397 | lastname-397 | 397 + firstname-398 | lastname-398 | 398 + firstname-399 | lastname-399 | 399 + firstname-4 | lastname-4 | 4 + firstname-40 | lastname-40 | 40 + firstname-400 | lastname-400 | 400 + firstname-401 | lastname-401 | 401 + firstname-402 | lastname-402 | 402 + firstname-403 | lastname-403 | 403 + firstname-404 | lastname-404 | 404 + firstname-405 | lastname-405 | 405 + firstname-406 | lastname-406 | 406 + firstname-407 | lastname-407 | 407 + firstname-408 | lastname-408 | 408 + firstname-409 | lastname-409 | 409 + firstname-41 | lastname-41 | 41 + firstname-410 | lastname-410 | 410 + firstname-411 | lastname-411 | 411 + firstname-412 | lastname-412 | 412 + firstname-413 | lastname-413 | 413 + firstname-414 | lastname-414 | 414 + firstname-415 | lastname-415 | 415 + firstname-416 | lastname-416 | 416 + firstname-417 | lastname-417 | 417 + firstname-418 | lastname-418 | 418 + firstname-419 | lastname-419 | 419 + firstname-42 | lastname-42 | 42 + firstname-420 | lastname-420 | 420 + firstname-421 | lastname-421 | 421 + firstname-422 | lastname-422 | 422 + firstname-423 | lastname-423 | 423 + firstname-424 | lastname-424 | 424 + firstname-425 | lastname-425 | 425 + firstname-426 | lastname-426 | 426 + firstname-427 | lastname-427 | 427 + firstname-428 | lastname-428 | 428 + firstname-429 | lastname-429 | 429 + firstname-43 | lastname-43 | 43 + firstname-430 | lastname-430 | 430 + firstname-431 | lastname-431 | 431 + firstname-432 | lastname-432 | 432 + firstname-433 | lastname-433 | 433 + firstname-434 | lastname-434 | 434 + firstname-435 | lastname-435 | 435 + firstname-436 | lastname-436 | 436 + firstname-437 | lastname-437 | 437 + firstname-438 | lastname-438 | 438 + firstname-439 | lastname-439 | 439 + firstname-44 | lastname-44 | 44 + firstname-440 | lastname-440 | 440 + firstname-441 | lastname-441 | 441 + firstname-442 | lastname-442 | 442 + firstname-443 | lastname-443 | 443 + firstname-444 | lastname-444 | 444 + firstname-445 | lastname-445 | 445 + firstname-446 | lastname-446 | 446 + firstname-447 | lastname-447 | 447 + firstname-448 | lastname-448 | 448 + firstname-449 | lastname-449 | 449 + firstname-45 | lastname-45 | 45 + firstname-450 | lastname-450 | 450 + firstname-451 | lastname-451 | 451 + firstname-452 | lastname-452 | 452 + firstname-453 | lastname-453 | 453 + firstname-454 | lastname-454 | 454 + firstname-455 | lastname-455 | 455 + firstname-456 | lastname-456 | 456 + firstname-457 | lastname-457 | 457 + firstname-458 | lastname-458 | 458 + firstname-459 | lastname-459 | 459 + firstname-46 | lastname-46 | 46 + firstname-460 | lastname-460 | 460 + firstname-461 | lastname-461 | 461 + firstname-462 | lastname-462 | 462 + firstname-463 | lastname-463 | 463 + firstname-464 | lastname-464 | 464 + firstname-465 | lastname-465 | 465 + firstname-466 | lastname-466 | 466 + firstname-467 | lastname-467 | 467 + firstname-468 | lastname-468 | 468 + firstname-469 | lastname-469 | 469 + firstname-47 | lastname-47 | 47 + firstname-470 | lastname-470 | 470 + firstname-471 | lastname-471 | 471 + firstname-472 | lastname-472 | 472 + firstname-473 | lastname-473 | 473 + firstname-474 | lastname-474 | 474 + firstname-475 | lastname-475 | 475 + firstname-476 | lastname-476 | 476 + firstname-477 | lastname-477 | 477 + firstname-478 | lastname-478 | 478 + firstname-479 | lastname-479 | 479 + firstname-48 | lastname-48 | 48 + firstname-480 | lastname-480 | 480 + firstname-481 | lastname-481 | 481 + firstname-482 | lastname-482 | 482 + firstname-483 | lastname-483 | 483 + firstname-484 | lastname-484 | 484 + firstname-485 | lastname-485 | 485 + firstname-486 | lastname-486 | 486 + firstname-487 | lastname-487 | 487 + firstname-488 | lastname-488 | 488 + firstname-489 | lastname-489 | 489 + firstname-49 | lastname-49 | 49 + firstname-490 | lastname-490 | 490 + firstname-491 | lastname-491 | 491 + firstname-492 | lastname-492 | 492 + firstname-493 | lastname-493 | 493 + firstname-494 | lastname-494 | 494 + firstname-495 | lastname-495 | 495 + firstname-496 | lastname-496 | 496 + firstname-497 | lastname-497 | 497 + firstname-498 | lastname-498 | 498 + firstname-499 | lastname-499 | 499 + firstname-5 | lastname-5 | 5 + firstname-50 | lastname-50 | 50 + firstname-500 | lastname-500 | 500 + firstname-501 | lastname-501 | 501 + firstname-502 | lastname-502 | 502 + firstname-503 | lastname-503 | 503 + firstname-504 | lastname-504 | 504 + firstname-505 | lastname-505 | 505 + firstname-506 | lastname-506 | 506 + firstname-507 | lastname-507 | 507 + firstname-508 | lastname-508 | 508 + firstname-509 | lastname-509 | 509 + firstname-51 | lastname-51 | 51 + firstname-510 | lastname-510 | 510 + firstname-511 | lastname-511 | 511 + firstname-512 | lastname-512 | 512 + firstname-513 | lastname-513 | 513 + firstname-514 | lastname-514 | 514 + firstname-515 | lastname-515 | 515 + firstname-516 | lastname-516 | 516 + firstname-517 | lastname-517 | 517 + firstname-518 | lastname-518 | 518 + firstname-519 | lastname-519 | 519 + firstname-52 | lastname-52 | 52 + firstname-520 | lastname-520 | 520 + firstname-521 | lastname-521 | 521 + firstname-522 | lastname-522 | 522 + firstname-523 | lastname-523 | 523 + firstname-524 | lastname-524 | 524 + firstname-525 | lastname-525 | 525 + firstname-526 | lastname-526 | 526 + firstname-527 | lastname-527 | 527 + firstname-528 | lastname-528 | 528 + firstname-529 | lastname-529 | 529 + firstname-53 | lastname-53 | 53 + firstname-530 | lastname-530 | 530 + firstname-531 | lastname-531 | 531 + firstname-532 | lastname-532 | 532 + firstname-533 | lastname-533 | 533 + firstname-534 | lastname-534 | 534 + firstname-535 | lastname-535 | 535 + firstname-536 | lastname-536 | 536 + firstname-537 | lastname-537 | 537 + firstname-538 | lastname-538 | 538 + firstname-539 | lastname-539 | 539 + firstname-54 | lastname-54 | 54 + firstname-540 | lastname-540 | 540 + firstname-541 | lastname-541 | 541 + firstname-542 | lastname-542 | 542 + firstname-543 | lastname-543 | 543 + firstname-544 | lastname-544 | 544 + firstname-545 | lastname-545 | 545 + firstname-546 | lastname-546 | 546 + firstname-547 | lastname-547 | 547 + firstname-548 | lastname-548 | 548 + firstname-549 | lastname-549 | 549 + firstname-55 | lastname-55 | 55 + firstname-550 | lastname-550 | 550 + firstname-551 | lastname-551 | 551 + firstname-552 | lastname-552 | 552 + firstname-553 | lastname-553 | 553 + firstname-554 | lastname-554 | 554 + firstname-555 | lastname-555 | 555 + firstname-556 | lastname-556 | 556 + firstname-557 | lastname-557 | 557 + firstname-558 | lastname-558 | 558 + firstname-559 | lastname-559 | 559 + firstname-56 | lastname-56 | 56 + firstname-560 | lastname-560 | 560 + firstname-561 | lastname-561 | 561 + firstname-562 | lastname-562 | 562 + firstname-563 | lastname-563 | 563 + firstname-564 | lastname-564 | 564 + firstname-565 | lastname-565 | 565 + firstname-566 | lastname-566 | 566 + firstname-567 | lastname-567 | 567 + firstname-568 | lastname-568 | 568 + firstname-569 | lastname-569 | 569 + firstname-57 | lastname-57 | 57 + firstname-570 | lastname-570 | 570 + firstname-571 | lastname-571 | 571 + firstname-572 | lastname-572 | 572 + firstname-573 | lastname-573 | 573 + firstname-574 | lastname-574 | 574 + firstname-575 | lastname-575 | 575 + firstname-576 | lastname-576 | 576 + firstname-577 | lastname-577 | 577 + firstname-578 | lastname-578 | 578 + firstname-579 | lastname-579 | 579 + firstname-58 | lastname-58 | 58 + firstname-580 | lastname-580 | 580 + firstname-581 | lastname-581 | 581 + firstname-582 | lastname-582 | 582 + firstname-583 | lastname-583 | 583 + firstname-584 | lastname-584 | 584 + firstname-585 | lastname-585 | 585 + firstname-586 | lastname-586 | 586 + firstname-587 | lastname-587 | 587 + firstname-588 | lastname-588 | 588 + firstname-589 | lastname-589 | 589 + firstname-59 | lastname-59 | 59 + firstname-590 | lastname-590 | 590 + firstname-591 | lastname-591 | 591 + firstname-592 | lastname-592 | 592 + firstname-593 | lastname-593 | 593 + firstname-594 | lastname-594 | 594 + firstname-595 | lastname-595 | 595 + firstname-596 | lastname-596 | 596 + firstname-597 | lastname-597 | 597 + firstname-598 | lastname-598 | 598 + firstname-599 | lastname-599 | 599 + firstname-6 | lastname-6 | 6 + firstname-60 | lastname-60 | 60 + firstname-600 | lastname-600 | 600 + firstname-601 | lastname-601 | 601 + firstname-602 | lastname-602 | 602 + firstname-603 | lastname-603 | 603 + firstname-604 | lastname-604 | 604 + firstname-605 | lastname-605 | 605 + firstname-606 | lastname-606 | 606 + firstname-607 | lastname-607 | 607 + firstname-608 | lastname-608 | 608 + firstname-609 | lastname-609 | 609 + firstname-61 | lastname-61 | 61 + firstname-610 | lastname-610 | 610 + firstname-611 | lastname-611 | 611 + firstname-612 | lastname-612 | 612 + firstname-613 | lastname-613 | 613 + firstname-614 | lastname-614 | 614 + firstname-615 | lastname-615 | 615 + firstname-616 | lastname-616 | 616 + firstname-617 | lastname-617 | 617 + firstname-618 | lastname-618 | 618 + firstname-619 | lastname-619 | 619 + firstname-62 | lastname-62 | 62 + firstname-620 | lastname-620 | 620 + firstname-621 | lastname-621 | 621 + firstname-622 | lastname-622 | 622 + firstname-623 | lastname-623 | 623 + firstname-624 | lastname-624 | 624 + firstname-625 | lastname-625 | 625 + firstname-626 | lastname-626 | 626 + firstname-627 | lastname-627 | 627 + firstname-628 | lastname-628 | 628 + firstname-629 | lastname-629 | 629 + firstname-63 | lastname-63 | 63 + firstname-630 | lastname-630 | 630 + firstname-631 | lastname-631 | 631 + firstname-632 | lastname-632 | 632 + firstname-633 | lastname-633 | 633 + firstname-634 | lastname-634 | 634 + firstname-635 | lastname-635 | 635 + firstname-636 | lastname-636 | 636 + firstname-637 | lastname-637 | 637 + firstname-638 | lastname-638 | 638 + firstname-639 | lastname-639 | 639 + firstname-64 | lastname-64 | 64 + firstname-640 | lastname-640 | 640 + firstname-641 | lastname-641 | 641 + firstname-642 | lastname-642 | 642 + firstname-643 | lastname-643 | 643 + firstname-644 | lastname-644 | 644 + firstname-645 | lastname-645 | 645 + firstname-646 | lastname-646 | 646 + firstname-647 | lastname-647 | 647 + firstname-648 | lastname-648 | 648 + firstname-649 | lastname-649 | 649 + firstname-65 | lastname-65 | 65 + firstname-650 | lastname-650 | 650 + firstname-651 | lastname-651 | 651 + firstname-652 | lastname-652 | 652 + firstname-653 | lastname-653 | 653 + firstname-654 | lastname-654 | 654 + firstname-655 | lastname-655 | 655 + firstname-656 | lastname-656 | 656 + firstname-657 | lastname-657 | 657 + firstname-658 | lastname-658 | 658 + firstname-659 | lastname-659 | 659 + firstname-66 | lastname-66 | 66 + firstname-660 | lastname-660 | 660 + firstname-661 | lastname-661 | 661 + firstname-662 | lastname-662 | 662 + firstname-663 | lastname-663 | 663 + firstname-664 | lastname-664 | 664 + firstname-665 | lastname-665 | 665 + firstname-666 | lastname-666 | 666 + firstname-667 | lastname-667 | 667 + firstname-668 | lastname-668 | 668 + firstname-669 | lastname-669 | 669 + firstname-67 | lastname-67 | 67 + firstname-670 | lastname-670 | 670 + firstname-671 | lastname-671 | 671 + firstname-672 | lastname-672 | 672 + firstname-673 | lastname-673 | 673 + firstname-674 | lastname-674 | 674 + firstname-675 | lastname-675 | 675 + firstname-676 | lastname-676 | 676 + firstname-677 | lastname-677 | 677 + firstname-678 | lastname-678 | 678 + firstname-679 | lastname-679 | 679 + firstname-68 | lastname-68 | 68 + firstname-680 | lastname-680 | 680 + firstname-681 | lastname-681 | 681 + firstname-682 | lastname-682 | 682 + firstname-683 | lastname-683 | 683 + firstname-684 | lastname-684 | 684 + firstname-685 | lastname-685 | 685 + firstname-686 | lastname-686 | 686 + firstname-687 | lastname-687 | 687 + firstname-688 | lastname-688 | 688 + firstname-689 | lastname-689 | 689 + firstname-69 | lastname-69 | 69 + firstname-690 | lastname-690 | 690 + firstname-691 | lastname-691 | 691 + firstname-692 | lastname-692 | 692 + firstname-693 | lastname-693 | 693 + firstname-694 | lastname-694 | 694 + firstname-695 | lastname-695 | 695 + firstname-696 | lastname-696 | 696 + firstname-697 | lastname-697 | 697 + firstname-698 | lastname-698 | 698 + firstname-699 | lastname-699 | 699 + firstname-7 | lastname-7 | 7 + firstname-70 | lastname-70 | 70 + firstname-700 | lastname-700 | 700 + firstname-701 | lastname-701 | 701 + firstname-702 | lastname-702 | 702 + firstname-703 | lastname-703 | 703 + firstname-704 | lastname-704 | 704 + firstname-705 | lastname-705 | 705 + firstname-706 | lastname-706 | 706 + firstname-707 | lastname-707 | 707 + firstname-708 | lastname-708 | 708 + firstname-709 | lastname-709 | 709 + firstname-71 | lastname-71 | 71 + firstname-710 | lastname-710 | 710 + firstname-711 | lastname-711 | 711 + firstname-712 | lastname-712 | 712 + firstname-713 | lastname-713 | 713 + firstname-714 | lastname-714 | 714 + firstname-715 | lastname-715 | 715 + firstname-716 | lastname-716 | 716 + firstname-717 | lastname-717 | 717 + firstname-718 | lastname-718 | 718 + firstname-719 | lastname-719 | 719 + firstname-72 | lastname-72 | 72 + firstname-720 | lastname-720 | 720 + firstname-721 | lastname-721 | 721 + firstname-722 | lastname-722 | 722 + firstname-723 | lastname-723 | 723 + firstname-724 | lastname-724 | 724 + firstname-725 | lastname-725 | 725 + firstname-726 | lastname-726 | 726 + firstname-727 | lastname-727 | 727 + firstname-728 | lastname-728 | 728 + firstname-729 | lastname-729 | 729 + firstname-73 | lastname-73 | 73 + firstname-730 | lastname-730 | 730 + firstname-731 | lastname-731 | 731 + firstname-732 | lastname-732 | 732 + firstname-733 | lastname-733 | 733 + firstname-734 | lastname-734 | 734 + firstname-735 | lastname-735 | 735 + firstname-736 | lastname-736 | 736 + firstname-737 | lastname-737 | 737 + firstname-738 | lastname-738 | 738 + firstname-739 | lastname-739 | 739 + firstname-74 | lastname-74 | 74 + firstname-740 | lastname-740 | 740 + firstname-741 | lastname-741 | 741 + firstname-742 | lastname-742 | 742 + firstname-743 | lastname-743 | 743 + firstname-744 | lastname-744 | 744 + firstname-745 | lastname-745 | 745 + firstname-746 | lastname-746 | 746 + firstname-747 | lastname-747 | 747 + firstname-748 | lastname-748 | 748 + firstname-749 | lastname-749 | 749 + firstname-75 | lastname-75 | 75 + firstname-750 | lastname-750 | 750 + firstname-751 | lastname-751 | 751 + firstname-752 | lastname-752 | 752 + firstname-753 | lastname-753 | 753 + firstname-754 | lastname-754 | 754 + firstname-755 | lastname-755 | 755 + firstname-756 | lastname-756 | 756 + firstname-757 | lastname-757 | 757 + firstname-758 | lastname-758 | 758 + firstname-759 | lastname-759 | 759 + firstname-76 | lastname-76 | 76 + firstname-760 | lastname-760 | 760 + firstname-761 | lastname-761 | 761 + firstname-762 | lastname-762 | 762 + firstname-763 | lastname-763 | 763 + firstname-764 | lastname-764 | 764 + firstname-765 | lastname-765 | 765 + firstname-766 | lastname-766 | 766 + firstname-767 | lastname-767 | 767 + firstname-768 | lastname-768 | 768 + firstname-769 | lastname-769 | 769 + firstname-77 | lastname-77 | 77 + firstname-770 | lastname-770 | 770 + firstname-771 | lastname-771 | 771 + firstname-772 | lastname-772 | 772 + firstname-773 | lastname-773 | 773 + firstname-774 | lastname-774 | 774 + firstname-775 | lastname-775 | 775 + firstname-776 | lastname-776 | 776 + firstname-777 | lastname-777 | 777 + firstname-778 | lastname-778 | 778 + firstname-779 | lastname-779 | 779 + firstname-78 | lastname-78 | 78 + firstname-780 | lastname-780 | 780 + firstname-781 | lastname-781 | 781 + firstname-782 | lastname-782 | 782 + firstname-783 | lastname-783 | 783 + firstname-784 | lastname-784 | 784 + firstname-785 | lastname-785 | 785 + firstname-786 | lastname-786 | 786 + firstname-787 | lastname-787 | 787 + firstname-788 | lastname-788 | 788 + firstname-789 | lastname-789 | 789 + firstname-79 | lastname-79 | 79 + firstname-790 | lastname-790 | 790 + firstname-791 | lastname-791 | 791 + firstname-792 | lastname-792 | 792 + firstname-793 | lastname-793 | 793 + firstname-794 | lastname-794 | 794 + firstname-795 | lastname-795 | 795 + firstname-796 | lastname-796 | 796 + firstname-797 | lastname-797 | 797 + firstname-798 | lastname-798 | 798 + firstname-799 | lastname-799 | 799 + firstname-8 | lastname-8 | 8 + firstname-80 | lastname-80 | 80 + firstname-800 | lastname-800 | 800 + firstname-801 | lastname-801 | 801 + firstname-802 | lastname-802 | 802 + firstname-803 | lastname-803 | 803 + firstname-804 | lastname-804 | 804 + firstname-805 | lastname-805 | 805 + firstname-806 | lastname-806 | 806 + firstname-807 | lastname-807 | 807 + firstname-808 | lastname-808 | 808 + firstname-809 | lastname-809 | 809 + firstname-81 | lastname-81 | 81 + firstname-810 | lastname-810 | 810 + firstname-811 | lastname-811 | 811 + firstname-812 | lastname-812 | 812 + firstname-813 | lastname-813 | 813 + firstname-814 | lastname-814 | 814 + firstname-815 | lastname-815 | 815 + firstname-816 | lastname-816 | 816 + firstname-817 | lastname-817 | 817 + firstname-818 | lastname-818 | 818 + firstname-819 | lastname-819 | 819 + firstname-82 | lastname-82 | 82 + firstname-820 | lastname-820 | 820 + firstname-821 | lastname-821 | 821 + firstname-822 | lastname-822 | 822 + firstname-823 | lastname-823 | 823 + firstname-824 | lastname-824 | 824 + firstname-825 | lastname-825 | 825 + firstname-826 | lastname-826 | 826 + firstname-827 | lastname-827 | 827 + firstname-828 | lastname-828 | 828 + firstname-829 | lastname-829 | 829 + firstname-83 | lastname-83 | 83 + firstname-830 | lastname-830 | 830 + firstname-831 | lastname-831 | 831 + firstname-832 | lastname-832 | 832 + firstname-833 | lastname-833 | 833 + firstname-834 | lastname-834 | 834 + firstname-835 | lastname-835 | 835 + firstname-836 | lastname-836 | 836 + firstname-837 | lastname-837 | 837 + firstname-838 | lastname-838 | 838 + firstname-839 | lastname-839 | 839 + firstname-84 | lastname-84 | 84 + firstname-840 | lastname-840 | 840 + firstname-841 | lastname-841 | 841 + firstname-842 | lastname-842 | 842 + firstname-843 | lastname-843 | 843 + firstname-844 | lastname-844 | 844 + firstname-845 | lastname-845 | 845 + firstname-846 | lastname-846 | 846 + firstname-847 | lastname-847 | 847 + firstname-848 | lastname-848 | 848 + firstname-849 | lastname-849 | 849 + firstname-85 | lastname-85 | 85 + firstname-850 | lastname-850 | 850 + firstname-851 | lastname-851 | 851 + firstname-852 | lastname-852 | 852 + firstname-853 | lastname-853 | 853 + firstname-854 | lastname-854 | 854 + firstname-855 | lastname-855 | 855 + firstname-856 | lastname-856 | 856 + firstname-857 | lastname-857 | 857 + firstname-858 | lastname-858 | 858 + firstname-859 | lastname-859 | 859 + firstname-86 | lastname-86 | 86 + firstname-860 | lastname-860 | 860 + firstname-861 | lastname-861 | 861 + firstname-862 | lastname-862 | 862 + firstname-863 | lastname-863 | 863 + firstname-864 | lastname-864 | 864 + firstname-865 | lastname-865 | 865 + firstname-866 | lastname-866 | 866 + firstname-867 | lastname-867 | 867 + firstname-868 | lastname-868 | 868 + firstname-869 | lastname-869 | 869 + firstname-87 | lastname-87 | 87 + firstname-870 | lastname-870 | 870 + firstname-871 | lastname-871 | 871 + firstname-872 | lastname-872 | 872 + firstname-873 | lastname-873 | 873 + firstname-874 | lastname-874 | 874 + firstname-875 | lastname-875 | 875 + firstname-876 | lastname-876 | 876 + firstname-877 | lastname-877 | 877 + firstname-878 | lastname-878 | 878 + firstname-879 | lastname-879 | 879 + firstname-88 | lastname-88 | 88 + firstname-880 | lastname-880 | 880 + firstname-881 | lastname-881 | 881 + firstname-882 | lastname-882 | 882 + firstname-883 | lastname-883 | 883 + firstname-884 | lastname-884 | 884 + firstname-885 | lastname-885 | 885 + firstname-886 | lastname-886 | 886 + firstname-887 | lastname-887 | 887 + firstname-888 | lastname-888 | 888 + firstname-889 | lastname-889 | 889 + firstname-89 | lastname-89 | 89 + firstname-890 | lastname-890 | 890 + firstname-891 | lastname-891 | 891 + firstname-892 | lastname-892 | 892 + firstname-893 | lastname-893 | 893 + firstname-894 | lastname-894 | 894 + firstname-895 | lastname-895 | 895 + firstname-896 | lastname-896 | 896 + firstname-897 | lastname-897 | 897 + firstname-898 | lastname-898 | 898 + firstname-899 | lastname-899 | 899 + firstname-9 | lastname-9 | 9 + firstname-90 | lastname-90 | 90 + firstname-900 | lastname-900 | 900 + firstname-901 | lastname-901 | 901 + firstname-902 | lastname-902 | 902 + firstname-903 | lastname-903 | 903 + firstname-904 | lastname-904 | 904 + firstname-905 | lastname-905 | 905 + firstname-906 | lastname-906 | 906 + firstname-907 | lastname-907 | 907 + firstname-908 | lastname-908 | 908 + firstname-909 | lastname-909 | 909 + firstname-91 | lastname-91 | 91 + firstname-910 | lastname-910 | 910 + firstname-911 | lastname-911 | 911 + firstname-912 | lastname-912 | 912 + firstname-913 | lastname-913 | 913 + firstname-914 | lastname-914 | 914 + firstname-915 | lastname-915 | 915 + firstname-916 | lastname-916 | 916 + firstname-917 | lastname-917 | 917 + firstname-918 | lastname-918 | 918 + firstname-919 | lastname-919 | 919 + firstname-92 | lastname-92 | 92 + firstname-920 | lastname-920 | 920 + firstname-921 | lastname-921 | 921 + firstname-922 | lastname-922 | 922 + firstname-923 | lastname-923 | 923 + firstname-924 | lastname-924 | 924 + firstname-925 | lastname-925 | 925 + firstname-926 | lastname-926 | 926 + firstname-927 | lastname-927 | 927 + firstname-928 | lastname-928 | 928 + firstname-929 | lastname-929 | 929 + firstname-93 | lastname-93 | 93 + firstname-930 | lastname-930 | 930 + firstname-931 | lastname-931 | 931 + firstname-932 | lastname-932 | 932 + firstname-933 | lastname-933 | 933 + firstname-934 | lastname-934 | 934 + firstname-935 | lastname-935 | 935 + firstname-936 | lastname-936 | 936 + firstname-937 | lastname-937 | 937 + firstname-938 | lastname-938 | 938 + firstname-939 | lastname-939 | 939 + firstname-94 | lastname-94 | 94 + firstname-940 | lastname-940 | 940 + firstname-941 | lastname-941 | 941 + firstname-942 | lastname-942 | 942 + firstname-943 | lastname-943 | 943 + firstname-944 | lastname-944 | 944 + firstname-945 | lastname-945 | 945 + firstname-946 | lastname-946 | 946 + firstname-947 | lastname-947 | 947 + firstname-948 | lastname-948 | 948 + firstname-949 | lastname-949 | 949 + firstname-95 | lastname-95 | 95 + firstname-950 | lastname-950 | 950 + firstname-951 | lastname-951 | 951 + firstname-952 | lastname-952 | 952 + firstname-953 | lastname-953 | 953 + firstname-954 | lastname-954 | 954 + firstname-955 | lastname-955 | 955 + firstname-956 | lastname-956 | 956 + firstname-957 | lastname-957 | 957 + firstname-958 | lastname-958 | 958 + firstname-959 | lastname-959 | 959 + firstname-96 | lastname-96 | 96 + firstname-960 | lastname-960 | 960 + firstname-961 | lastname-961 | 961 + firstname-962 | lastname-962 | 962 + firstname-963 | lastname-963 | 963 + firstname-964 | lastname-964 | 964 + firstname-965 | lastname-965 | 965 + firstname-966 | lastname-966 | 966 + firstname-967 | lastname-967 | 967 + firstname-968 | lastname-968 | 968 + firstname-969 | lastname-969 | 969 + firstname-97 | lastname-97 | 97 + firstname-970 | lastname-970 | 970 + firstname-971 | lastname-971 | 971 + firstname-972 | lastname-972 | 972 + firstname-973 | lastname-973 | 973 + firstname-974 | lastname-974 | 974 + firstname-975 | lastname-975 | 975 + firstname-976 | lastname-976 | 976 + firstname-977 | lastname-977 | 977 + firstname-978 | lastname-978 | 978 + firstname-979 | lastname-979 | 979 + firstname-98 | lastname-98 | 98 + firstname-980 | lastname-980 | 980 + firstname-981 | lastname-981 | 981 + firstname-982 | lastname-982 | 982 + firstname-983 | lastname-983 | 983 + firstname-984 | lastname-984 | 984 + firstname-985 | lastname-985 | 985 + firstname-986 | lastname-986 | 986 + firstname-987 | lastname-987 | 987 + firstname-988 | lastname-988 | 988 + firstname-989 | lastname-989 | 989 + firstname-99 | lastname-99 | 99 + firstname-990 | lastname-990 | 990 + firstname-991 | lastname-991 | 991 + firstname-992 | lastname-992 | 992 + firstname-993 | lastname-993 | 993 + firstname-994 | lastname-994 | 994 + firstname-995 | lastname-995 | 995 + firstname-996 | lastname-996 | 996 + firstname-997 | lastname-997 | 997 + firstname-998 | lastname-998 | 998 + firstname-999 | lastname-999 | 999 +(2000 rows) + +-- enable caching +SET columnar.enable_column_cache = 't'; +-- the results should be the same as above +SELECT firstname, + lastname, + SUM(id) + FROM big_table + WHERE id < 1000 + GROUP BY firstname, + lastname +UNION +SELECT firstname, + lastname, + SUM(id) + FROM big_table + WHERE id BETWEEN 15000 AND 16000 + GROUP BY firstname, + lastname + ORDER BY firstname; + firstname | lastname | sum +---------------+--------------+----- + firstname-1 | lastname-1 | 1 + firstname-10 | lastname-10 | 10 + firstname-100 | lastname-100 | 100 + firstname-101 | lastname-101 | 101 + firstname-102 | lastname-102 | 102 + firstname-103 | lastname-103 | 103 + firstname-104 | lastname-104 | 104 + firstname-105 | lastname-105 | 105 + firstname-106 | lastname-106 | 106 + firstname-107 | lastname-107 | 107 + firstname-108 | lastname-108 | 108 + firstname-109 | lastname-109 | 109 + firstname-11 | lastname-11 | 11 + firstname-110 | lastname-110 | 110 + firstname-111 | lastname-111 | 111 + firstname-112 | lastname-112 | 112 + firstname-113 | lastname-113 | 113 + firstname-114 | lastname-114 | 114 + firstname-115 | lastname-115 | 115 + firstname-116 | lastname-116 | 116 + firstname-117 | lastname-117 | 117 + firstname-118 | lastname-118 | 118 + firstname-119 | lastname-119 | 119 + firstname-12 | lastname-12 | 12 + firstname-120 | lastname-120 | 120 + firstname-121 | lastname-121 | 121 + firstname-122 | lastname-122 | 122 + firstname-123 | lastname-123 | 123 + firstname-124 | lastname-124 | 124 + firstname-125 | lastname-125 | 125 + firstname-126 | lastname-126 | 126 + firstname-127 | lastname-127 | 127 + firstname-128 | lastname-128 | 128 + firstname-129 | lastname-129 | 129 + firstname-13 | lastname-13 | 13 + firstname-130 | lastname-130 | 130 + firstname-131 | lastname-131 | 131 + firstname-132 | lastname-132 | 132 + firstname-133 | lastname-133 | 133 + firstname-134 | lastname-134 | 134 + firstname-135 | lastname-135 | 135 + firstname-136 | lastname-136 | 136 + firstname-137 | lastname-137 | 137 + firstname-138 | lastname-138 | 138 + firstname-139 | lastname-139 | 139 + firstname-14 | lastname-14 | 14 + firstname-140 | lastname-140 | 140 + firstname-141 | lastname-141 | 141 + firstname-142 | lastname-142 | 142 + firstname-143 | lastname-143 | 143 + firstname-144 | lastname-144 | 144 + firstname-145 | lastname-145 | 145 + firstname-146 | lastname-146 | 146 + firstname-147 | lastname-147 | 147 + firstname-148 | lastname-148 | 148 + firstname-149 | lastname-149 | 149 + firstname-15 | lastname-15 | 15 + firstname-150 | lastname-150 | 150 + firstname-151 | lastname-151 | 151 + firstname-152 | lastname-152 | 152 + firstname-153 | lastname-153 | 153 + firstname-154 | lastname-154 | 154 + firstname-155 | lastname-155 | 155 + firstname-156 | lastname-156 | 156 + firstname-157 | lastname-157 | 157 + firstname-158 | lastname-158 | 158 + firstname-159 | lastname-159 | 159 + firstname-16 | lastname-16 | 16 + firstname-160 | lastname-160 | 160 + firstname-161 | lastname-161 | 161 + firstname-162 | lastname-162 | 162 + firstname-163 | lastname-163 | 163 + firstname-164 | lastname-164 | 164 + firstname-165 | lastname-165 | 165 + firstname-166 | lastname-166 | 166 + firstname-167 | lastname-167 | 167 + firstname-168 | lastname-168 | 168 + firstname-169 | lastname-169 | 169 + firstname-17 | lastname-17 | 17 + firstname-170 | lastname-170 | 170 + firstname-171 | lastname-171 | 171 + firstname-172 | lastname-172 | 172 + firstname-173 | lastname-173 | 173 + firstname-174 | lastname-174 | 174 + firstname-175 | lastname-175 | 175 + firstname-176 | lastname-176 | 176 + firstname-177 | lastname-177 | 177 + firstname-178 | lastname-178 | 178 + firstname-179 | lastname-179 | 179 + firstname-18 | lastname-18 | 18 + firstname-180 | lastname-180 | 180 + firstname-181 | lastname-181 | 181 + firstname-182 | lastname-182 | 182 + firstname-183 | lastname-183 | 183 + firstname-184 | lastname-184 | 184 + firstname-185 | lastname-185 | 185 + firstname-186 | lastname-186 | 186 + firstname-187 | lastname-187 | 187 + firstname-188 | lastname-188 | 188 + firstname-189 | lastname-189 | 189 + firstname-19 | lastname-19 | 19 + firstname-190 | lastname-190 | 190 + firstname-191 | lastname-191 | 191 + firstname-192 | lastname-192 | 192 + firstname-193 | lastname-193 | 193 + firstname-194 | lastname-194 | 194 + firstname-195 | lastname-195 | 195 + firstname-196 | lastname-196 | 196 + firstname-197 | lastname-197 | 197 + firstname-198 | lastname-198 | 198 + firstname-199 | lastname-199 | 199 + firstname-2 | lastname-2 | 2 + firstname-20 | lastname-20 | 20 + firstname-200 | lastname-200 | 200 + firstname-201 | lastname-201 | 201 + firstname-202 | lastname-202 | 202 + firstname-203 | lastname-203 | 203 + firstname-204 | lastname-204 | 204 + firstname-205 | lastname-205 | 205 + firstname-206 | lastname-206 | 206 + firstname-207 | lastname-207 | 207 + firstname-208 | lastname-208 | 208 + firstname-209 | lastname-209 | 209 + firstname-21 | lastname-21 | 21 + firstname-210 | lastname-210 | 210 + firstname-211 | lastname-211 | 211 + firstname-212 | lastname-212 | 212 + firstname-213 | lastname-213 | 213 + firstname-214 | lastname-214 | 214 + firstname-215 | lastname-215 | 215 + firstname-216 | lastname-216 | 216 + firstname-217 | lastname-217 | 217 + firstname-218 | lastname-218 | 218 + firstname-219 | lastname-219 | 219 + firstname-22 | lastname-22 | 22 + firstname-220 | lastname-220 | 220 + firstname-221 | lastname-221 | 221 + firstname-222 | lastname-222 | 222 + firstname-223 | lastname-223 | 223 + firstname-224 | lastname-224 | 224 + firstname-225 | lastname-225 | 225 + firstname-226 | lastname-226 | 226 + firstname-227 | lastname-227 | 227 + firstname-228 | lastname-228 | 228 + firstname-229 | lastname-229 | 229 + firstname-23 | lastname-23 | 23 + firstname-230 | lastname-230 | 230 + firstname-231 | lastname-231 | 231 + firstname-232 | lastname-232 | 232 + firstname-233 | lastname-233 | 233 + firstname-234 | lastname-234 | 234 + firstname-235 | lastname-235 | 235 + firstname-236 | lastname-236 | 236 + firstname-237 | lastname-237 | 237 + firstname-238 | lastname-238 | 238 + firstname-239 | lastname-239 | 239 + firstname-24 | lastname-24 | 24 + firstname-240 | lastname-240 | 240 + firstname-241 | lastname-241 | 241 + firstname-242 | lastname-242 | 242 + firstname-243 | lastname-243 | 243 + firstname-244 | lastname-244 | 244 + firstname-245 | lastname-245 | 245 + firstname-246 | lastname-246 | 246 + firstname-247 | lastname-247 | 247 + firstname-248 | lastname-248 | 248 + firstname-249 | lastname-249 | 249 + firstname-25 | lastname-25 | 25 + firstname-250 | lastname-250 | 250 + firstname-251 | lastname-251 | 251 + firstname-252 | lastname-252 | 252 + firstname-253 | lastname-253 | 253 + firstname-254 | lastname-254 | 254 + firstname-255 | lastname-255 | 255 + firstname-256 | lastname-256 | 256 + firstname-257 | lastname-257 | 257 + firstname-258 | lastname-258 | 258 + firstname-259 | lastname-259 | 259 + firstname-26 | lastname-26 | 26 + firstname-260 | lastname-260 | 260 + firstname-261 | lastname-261 | 261 + firstname-262 | lastname-262 | 262 + firstname-263 | lastname-263 | 263 + firstname-264 | lastname-264 | 264 + firstname-265 | lastname-265 | 265 + firstname-266 | lastname-266 | 266 + firstname-267 | lastname-267 | 267 + firstname-268 | lastname-268 | 268 + firstname-269 | lastname-269 | 269 + firstname-27 | lastname-27 | 27 + firstname-270 | lastname-270 | 270 + firstname-271 | lastname-271 | 271 + firstname-272 | lastname-272 | 272 + firstname-273 | lastname-273 | 273 + firstname-274 | lastname-274 | 274 + firstname-275 | lastname-275 | 275 + firstname-276 | lastname-276 | 276 + firstname-277 | lastname-277 | 277 + firstname-278 | lastname-278 | 278 + firstname-279 | lastname-279 | 279 + firstname-28 | lastname-28 | 28 + firstname-280 | lastname-280 | 280 + firstname-281 | lastname-281 | 281 + firstname-282 | lastname-282 | 282 + firstname-283 | lastname-283 | 283 + firstname-284 | lastname-284 | 284 + firstname-285 | lastname-285 | 285 + firstname-286 | lastname-286 | 286 + firstname-287 | lastname-287 | 287 + firstname-288 | lastname-288 | 288 + firstname-289 | lastname-289 | 289 + firstname-29 | lastname-29 | 29 + firstname-290 | lastname-290 | 290 + firstname-291 | lastname-291 | 291 + firstname-292 | lastname-292 | 292 + firstname-293 | lastname-293 | 293 + firstname-294 | lastname-294 | 294 + firstname-295 | lastname-295 | 295 + firstname-296 | lastname-296 | 296 + firstname-297 | lastname-297 | 297 + firstname-298 | lastname-298 | 298 + firstname-299 | lastname-299 | 299 + firstname-3 | lastname-3 | 3 + firstname-30 | lastname-30 | 30 + firstname-300 | lastname-300 | 300 + firstname-301 | lastname-301 | 301 + firstname-302 | lastname-302 | 302 + firstname-303 | lastname-303 | 303 + firstname-304 | lastname-304 | 304 + firstname-305 | lastname-305 | 305 + firstname-306 | lastname-306 | 306 + firstname-307 | lastname-307 | 307 + firstname-308 | lastname-308 | 308 + firstname-309 | lastname-309 | 309 + firstname-31 | lastname-31 | 31 + firstname-310 | lastname-310 | 310 + firstname-311 | lastname-311 | 311 + firstname-312 | lastname-312 | 312 + firstname-313 | lastname-313 | 313 + firstname-314 | lastname-314 | 314 + firstname-315 | lastname-315 | 315 + firstname-316 | lastname-316 | 316 + firstname-317 | lastname-317 | 317 + firstname-318 | lastname-318 | 318 + firstname-319 | lastname-319 | 319 + firstname-32 | lastname-32 | 32 + firstname-320 | lastname-320 | 320 + firstname-321 | lastname-321 | 321 + firstname-322 | lastname-322 | 322 + firstname-323 | lastname-323 | 323 + firstname-324 | lastname-324 | 324 + firstname-325 | lastname-325 | 325 + firstname-326 | lastname-326 | 326 + firstname-327 | lastname-327 | 327 + firstname-328 | lastname-328 | 328 + firstname-329 | lastname-329 | 329 + firstname-33 | lastname-33 | 33 + firstname-330 | lastname-330 | 330 + firstname-331 | lastname-331 | 331 + firstname-332 | lastname-332 | 332 + firstname-333 | lastname-333 | 333 + firstname-334 | lastname-334 | 334 + firstname-335 | lastname-335 | 335 + firstname-336 | lastname-336 | 336 + firstname-337 | lastname-337 | 337 + firstname-338 | lastname-338 | 338 + firstname-339 | lastname-339 | 339 + firstname-34 | lastname-34 | 34 + firstname-340 | lastname-340 | 340 + firstname-341 | lastname-341 | 341 + firstname-342 | lastname-342 | 342 + firstname-343 | lastname-343 | 343 + firstname-344 | lastname-344 | 344 + firstname-345 | lastname-345 | 345 + firstname-346 | lastname-346 | 346 + firstname-347 | lastname-347 | 347 + firstname-348 | lastname-348 | 348 + firstname-349 | lastname-349 | 349 + firstname-35 | lastname-35 | 35 + firstname-350 | lastname-350 | 350 + firstname-351 | lastname-351 | 351 + firstname-352 | lastname-352 | 352 + firstname-353 | lastname-353 | 353 + firstname-354 | lastname-354 | 354 + firstname-355 | lastname-355 | 355 + firstname-356 | lastname-356 | 356 + firstname-357 | lastname-357 | 357 + firstname-358 | lastname-358 | 358 + firstname-359 | lastname-359 | 359 + firstname-36 | lastname-36 | 36 + firstname-360 | lastname-360 | 360 + firstname-361 | lastname-361 | 361 + firstname-362 | lastname-362 | 362 + firstname-363 | lastname-363 | 363 + firstname-364 | lastname-364 | 364 + firstname-365 | lastname-365 | 365 + firstname-366 | lastname-366 | 366 + firstname-367 | lastname-367 | 367 + firstname-368 | lastname-368 | 368 + firstname-369 | lastname-369 | 369 + firstname-37 | lastname-37 | 37 + firstname-370 | lastname-370 | 370 + firstname-371 | lastname-371 | 371 + firstname-372 | lastname-372 | 372 + firstname-373 | lastname-373 | 373 + firstname-374 | lastname-374 | 374 + firstname-375 | lastname-375 | 375 + firstname-376 | lastname-376 | 376 + firstname-377 | lastname-377 | 377 + firstname-378 | lastname-378 | 378 + firstname-379 | lastname-379 | 379 + firstname-38 | lastname-38 | 38 + firstname-380 | lastname-380 | 380 + firstname-381 | lastname-381 | 381 + firstname-382 | lastname-382 | 382 + firstname-383 | lastname-383 | 383 + firstname-384 | lastname-384 | 384 + firstname-385 | lastname-385 | 385 + firstname-386 | lastname-386 | 386 + firstname-387 | lastname-387 | 387 + firstname-388 | lastname-388 | 388 + firstname-389 | lastname-389 | 389 + firstname-39 | lastname-39 | 39 + firstname-390 | lastname-390 | 390 + firstname-391 | lastname-391 | 391 + firstname-392 | lastname-392 | 392 + firstname-393 | lastname-393 | 393 + firstname-394 | lastname-394 | 394 + firstname-395 | lastname-395 | 395 + firstname-396 | lastname-396 | 396 + firstname-397 | lastname-397 | 397 + firstname-398 | lastname-398 | 398 + firstname-399 | lastname-399 | 399 + firstname-4 | lastname-4 | 4 + firstname-40 | lastname-40 | 40 + firstname-400 | lastname-400 | 400 + firstname-401 | lastname-401 | 401 + firstname-402 | lastname-402 | 402 + firstname-403 | lastname-403 | 403 + firstname-404 | lastname-404 | 404 + firstname-405 | lastname-405 | 405 + firstname-406 | lastname-406 | 406 + firstname-407 | lastname-407 | 407 + firstname-408 | lastname-408 | 408 + firstname-409 | lastname-409 | 409 + firstname-41 | lastname-41 | 41 + firstname-410 | lastname-410 | 410 + firstname-411 | lastname-411 | 411 + firstname-412 | lastname-412 | 412 + firstname-413 | lastname-413 | 413 + firstname-414 | lastname-414 | 414 + firstname-415 | lastname-415 | 415 + firstname-416 | lastname-416 | 416 + firstname-417 | lastname-417 | 417 + firstname-418 | lastname-418 | 418 + firstname-419 | lastname-419 | 419 + firstname-42 | lastname-42 | 42 + firstname-420 | lastname-420 | 420 + firstname-421 | lastname-421 | 421 + firstname-422 | lastname-422 | 422 + firstname-423 | lastname-423 | 423 + firstname-424 | lastname-424 | 424 + firstname-425 | lastname-425 | 425 + firstname-426 | lastname-426 | 426 + firstname-427 | lastname-427 | 427 + firstname-428 | lastname-428 | 428 + firstname-429 | lastname-429 | 429 + firstname-43 | lastname-43 | 43 + firstname-430 | lastname-430 | 430 + firstname-431 | lastname-431 | 431 + firstname-432 | lastname-432 | 432 + firstname-433 | lastname-433 | 433 + firstname-434 | lastname-434 | 434 + firstname-435 | lastname-435 | 435 + firstname-436 | lastname-436 | 436 + firstname-437 | lastname-437 | 437 + firstname-438 | lastname-438 | 438 + firstname-439 | lastname-439 | 439 + firstname-44 | lastname-44 | 44 + firstname-440 | lastname-440 | 440 + firstname-441 | lastname-441 | 441 + firstname-442 | lastname-442 | 442 + firstname-443 | lastname-443 | 443 + firstname-444 | lastname-444 | 444 + firstname-445 | lastname-445 | 445 + firstname-446 | lastname-446 | 446 + firstname-447 | lastname-447 | 447 + firstname-448 | lastname-448 | 448 + firstname-449 | lastname-449 | 449 + firstname-45 | lastname-45 | 45 + firstname-450 | lastname-450 | 450 + firstname-451 | lastname-451 | 451 + firstname-452 | lastname-452 | 452 + firstname-453 | lastname-453 | 453 + firstname-454 | lastname-454 | 454 + firstname-455 | lastname-455 | 455 + firstname-456 | lastname-456 | 456 + firstname-457 | lastname-457 | 457 + firstname-458 | lastname-458 | 458 + firstname-459 | lastname-459 | 459 + firstname-46 | lastname-46 | 46 + firstname-460 | lastname-460 | 460 + firstname-461 | lastname-461 | 461 + firstname-462 | lastname-462 | 462 + firstname-463 | lastname-463 | 463 + firstname-464 | lastname-464 | 464 + firstname-465 | lastname-465 | 465 + firstname-466 | lastname-466 | 466 + firstname-467 | lastname-467 | 467 + firstname-468 | lastname-468 | 468 + firstname-469 | lastname-469 | 469 + firstname-47 | lastname-47 | 47 + firstname-470 | lastname-470 | 470 + firstname-471 | lastname-471 | 471 + firstname-472 | lastname-472 | 472 + firstname-473 | lastname-473 | 473 + firstname-474 | lastname-474 | 474 + firstname-475 | lastname-475 | 475 + firstname-476 | lastname-476 | 476 + firstname-477 | lastname-477 | 477 + firstname-478 | lastname-478 | 478 + firstname-479 | lastname-479 | 479 + firstname-48 | lastname-48 | 48 + firstname-480 | lastname-480 | 480 + firstname-481 | lastname-481 | 481 + firstname-482 | lastname-482 | 482 + firstname-483 | lastname-483 | 483 + firstname-484 | lastname-484 | 484 + firstname-485 | lastname-485 | 485 + firstname-486 | lastname-486 | 486 + firstname-487 | lastname-487 | 487 + firstname-488 | lastname-488 | 488 + firstname-489 | lastname-489 | 489 + firstname-49 | lastname-49 | 49 + firstname-490 | lastname-490 | 490 + firstname-491 | lastname-491 | 491 + firstname-492 | lastname-492 | 492 + firstname-493 | lastname-493 | 493 + firstname-494 | lastname-494 | 494 + firstname-495 | lastname-495 | 495 + firstname-496 | lastname-496 | 496 + firstname-497 | lastname-497 | 497 + firstname-498 | lastname-498 | 498 + firstname-499 | lastname-499 | 499 + firstname-5 | lastname-5 | 5 + firstname-50 | lastname-50 | 50 + firstname-500 | lastname-500 | 500 + firstname-501 | lastname-501 | 501 + firstname-502 | lastname-502 | 502 + firstname-503 | lastname-503 | 503 + firstname-504 | lastname-504 | 504 + firstname-505 | lastname-505 | 505 + firstname-506 | lastname-506 | 506 + firstname-507 | lastname-507 | 507 + firstname-508 | lastname-508 | 508 + firstname-509 | lastname-509 | 509 + firstname-51 | lastname-51 | 51 + firstname-510 | lastname-510 | 510 + firstname-511 | lastname-511 | 511 + firstname-512 | lastname-512 | 512 + firstname-513 | lastname-513 | 513 + firstname-514 | lastname-514 | 514 + firstname-515 | lastname-515 | 515 + firstname-516 | lastname-516 | 516 + firstname-517 | lastname-517 | 517 + firstname-518 | lastname-518 | 518 + firstname-519 | lastname-519 | 519 + firstname-52 | lastname-52 | 52 + firstname-520 | lastname-520 | 520 + firstname-521 | lastname-521 | 521 + firstname-522 | lastname-522 | 522 + firstname-523 | lastname-523 | 523 + firstname-524 | lastname-524 | 524 + firstname-525 | lastname-525 | 525 + firstname-526 | lastname-526 | 526 + firstname-527 | lastname-527 | 527 + firstname-528 | lastname-528 | 528 + firstname-529 | lastname-529 | 529 + firstname-53 | lastname-53 | 53 + firstname-530 | lastname-530 | 530 + firstname-531 | lastname-531 | 531 + firstname-532 | lastname-532 | 532 + firstname-533 | lastname-533 | 533 + firstname-534 | lastname-534 | 534 + firstname-535 | lastname-535 | 535 + firstname-536 | lastname-536 | 536 + firstname-537 | lastname-537 | 537 + firstname-538 | lastname-538 | 538 + firstname-539 | lastname-539 | 539 + firstname-54 | lastname-54 | 54 + firstname-540 | lastname-540 | 540 + firstname-541 | lastname-541 | 541 + firstname-542 | lastname-542 | 542 + firstname-543 | lastname-543 | 543 + firstname-544 | lastname-544 | 544 + firstname-545 | lastname-545 | 545 + firstname-546 | lastname-546 | 546 + firstname-547 | lastname-547 | 547 + firstname-548 | lastname-548 | 548 + firstname-549 | lastname-549 | 549 + firstname-55 | lastname-55 | 55 + firstname-550 | lastname-550 | 550 + firstname-551 | lastname-551 | 551 + firstname-552 | lastname-552 | 552 + firstname-553 | lastname-553 | 553 + firstname-554 | lastname-554 | 554 + firstname-555 | lastname-555 | 555 + firstname-556 | lastname-556 | 556 + firstname-557 | lastname-557 | 557 + firstname-558 | lastname-558 | 558 + firstname-559 | lastname-559 | 559 + firstname-56 | lastname-56 | 56 + firstname-560 | lastname-560 | 560 + firstname-561 | lastname-561 | 561 + firstname-562 | lastname-562 | 562 + firstname-563 | lastname-563 | 563 + firstname-564 | lastname-564 | 564 + firstname-565 | lastname-565 | 565 + firstname-566 | lastname-566 | 566 + firstname-567 | lastname-567 | 567 + firstname-568 | lastname-568 | 568 + firstname-569 | lastname-569 | 569 + firstname-57 | lastname-57 | 57 + firstname-570 | lastname-570 | 570 + firstname-571 | lastname-571 | 571 + firstname-572 | lastname-572 | 572 + firstname-573 | lastname-573 | 573 + firstname-574 | lastname-574 | 574 + firstname-575 | lastname-575 | 575 + firstname-576 | lastname-576 | 576 + firstname-577 | lastname-577 | 577 + firstname-578 | lastname-578 | 578 + firstname-579 | lastname-579 | 579 + firstname-58 | lastname-58 | 58 + firstname-580 | lastname-580 | 580 + firstname-581 | lastname-581 | 581 + firstname-582 | lastname-582 | 582 + firstname-583 | lastname-583 | 583 + firstname-584 | lastname-584 | 584 + firstname-585 | lastname-585 | 585 + firstname-586 | lastname-586 | 586 + firstname-587 | lastname-587 | 587 + firstname-588 | lastname-588 | 588 + firstname-589 | lastname-589 | 589 + firstname-59 | lastname-59 | 59 + firstname-590 | lastname-590 | 590 + firstname-591 | lastname-591 | 591 + firstname-592 | lastname-592 | 592 + firstname-593 | lastname-593 | 593 + firstname-594 | lastname-594 | 594 + firstname-595 | lastname-595 | 595 + firstname-596 | lastname-596 | 596 + firstname-597 | lastname-597 | 597 + firstname-598 | lastname-598 | 598 + firstname-599 | lastname-599 | 599 + firstname-6 | lastname-6 | 6 + firstname-60 | lastname-60 | 60 + firstname-600 | lastname-600 | 600 + firstname-601 | lastname-601 | 601 + firstname-602 | lastname-602 | 602 + firstname-603 | lastname-603 | 603 + firstname-604 | lastname-604 | 604 + firstname-605 | lastname-605 | 605 + firstname-606 | lastname-606 | 606 + firstname-607 | lastname-607 | 607 + firstname-608 | lastname-608 | 608 + firstname-609 | lastname-609 | 609 + firstname-61 | lastname-61 | 61 + firstname-610 | lastname-610 | 610 + firstname-611 | lastname-611 | 611 + firstname-612 | lastname-612 | 612 + firstname-613 | lastname-613 | 613 + firstname-614 | lastname-614 | 614 + firstname-615 | lastname-615 | 615 + firstname-616 | lastname-616 | 616 + firstname-617 | lastname-617 | 617 + firstname-618 | lastname-618 | 618 + firstname-619 | lastname-619 | 619 + firstname-62 | lastname-62 | 62 + firstname-620 | lastname-620 | 620 + firstname-621 | lastname-621 | 621 + firstname-622 | lastname-622 | 622 + firstname-623 | lastname-623 | 623 + firstname-624 | lastname-624 | 624 + firstname-625 | lastname-625 | 625 + firstname-626 | lastname-626 | 626 + firstname-627 | lastname-627 | 627 + firstname-628 | lastname-628 | 628 + firstname-629 | lastname-629 | 629 + firstname-63 | lastname-63 | 63 + firstname-630 | lastname-630 | 630 + firstname-631 | lastname-631 | 631 + firstname-632 | lastname-632 | 632 + firstname-633 | lastname-633 | 633 + firstname-634 | lastname-634 | 634 + firstname-635 | lastname-635 | 635 + firstname-636 | lastname-636 | 636 + firstname-637 | lastname-637 | 637 + firstname-638 | lastname-638 | 638 + firstname-639 | lastname-639 | 639 + firstname-64 | lastname-64 | 64 + firstname-640 | lastname-640 | 640 + firstname-641 | lastname-641 | 641 + firstname-642 | lastname-642 | 642 + firstname-643 | lastname-643 | 643 + firstname-644 | lastname-644 | 644 + firstname-645 | lastname-645 | 645 + firstname-646 | lastname-646 | 646 + firstname-647 | lastname-647 | 647 + firstname-648 | lastname-648 | 648 + firstname-649 | lastname-649 | 649 + firstname-65 | lastname-65 | 65 + firstname-650 | lastname-650 | 650 + firstname-651 | lastname-651 | 651 + firstname-652 | lastname-652 | 652 + firstname-653 | lastname-653 | 653 + firstname-654 | lastname-654 | 654 + firstname-655 | lastname-655 | 655 + firstname-656 | lastname-656 | 656 + firstname-657 | lastname-657 | 657 + firstname-658 | lastname-658 | 658 + firstname-659 | lastname-659 | 659 + firstname-66 | lastname-66 | 66 + firstname-660 | lastname-660 | 660 + firstname-661 | lastname-661 | 661 + firstname-662 | lastname-662 | 662 + firstname-663 | lastname-663 | 663 + firstname-664 | lastname-664 | 664 + firstname-665 | lastname-665 | 665 + firstname-666 | lastname-666 | 666 + firstname-667 | lastname-667 | 667 + firstname-668 | lastname-668 | 668 + firstname-669 | lastname-669 | 669 + firstname-67 | lastname-67 | 67 + firstname-670 | lastname-670 | 670 + firstname-671 | lastname-671 | 671 + firstname-672 | lastname-672 | 672 + firstname-673 | lastname-673 | 673 + firstname-674 | lastname-674 | 674 + firstname-675 | lastname-675 | 675 + firstname-676 | lastname-676 | 676 + firstname-677 | lastname-677 | 677 + firstname-678 | lastname-678 | 678 + firstname-679 | lastname-679 | 679 + firstname-68 | lastname-68 | 68 + firstname-680 | lastname-680 | 680 + firstname-681 | lastname-681 | 681 + firstname-682 | lastname-682 | 682 + firstname-683 | lastname-683 | 683 + firstname-684 | lastname-684 | 684 + firstname-685 | lastname-685 | 685 + firstname-686 | lastname-686 | 686 + firstname-687 | lastname-687 | 687 + firstname-688 | lastname-688 | 688 + firstname-689 | lastname-689 | 689 + firstname-69 | lastname-69 | 69 + firstname-690 | lastname-690 | 690 + firstname-691 | lastname-691 | 691 + firstname-692 | lastname-692 | 692 + firstname-693 | lastname-693 | 693 + firstname-694 | lastname-694 | 694 + firstname-695 | lastname-695 | 695 + firstname-696 | lastname-696 | 696 + firstname-697 | lastname-697 | 697 + firstname-698 | lastname-698 | 698 + firstname-699 | lastname-699 | 699 + firstname-7 | lastname-7 | 7 + firstname-70 | lastname-70 | 70 + firstname-700 | lastname-700 | 700 + firstname-701 | lastname-701 | 701 + firstname-702 | lastname-702 | 702 + firstname-703 | lastname-703 | 703 + firstname-704 | lastname-704 | 704 + firstname-705 | lastname-705 | 705 + firstname-706 | lastname-706 | 706 + firstname-707 | lastname-707 | 707 + firstname-708 | lastname-708 | 708 + firstname-709 | lastname-709 | 709 + firstname-71 | lastname-71 | 71 + firstname-710 | lastname-710 | 710 + firstname-711 | lastname-711 | 711 + firstname-712 | lastname-712 | 712 + firstname-713 | lastname-713 | 713 + firstname-714 | lastname-714 | 714 + firstname-715 | lastname-715 | 715 + firstname-716 | lastname-716 | 716 + firstname-717 | lastname-717 | 717 + firstname-718 | lastname-718 | 718 + firstname-719 | lastname-719 | 719 + firstname-72 | lastname-72 | 72 + firstname-720 | lastname-720 | 720 + firstname-721 | lastname-721 | 721 + firstname-722 | lastname-722 | 722 + firstname-723 | lastname-723 | 723 + firstname-724 | lastname-724 | 724 + firstname-725 | lastname-725 | 725 + firstname-726 | lastname-726 | 726 + firstname-727 | lastname-727 | 727 + firstname-728 | lastname-728 | 728 + firstname-729 | lastname-729 | 729 + firstname-73 | lastname-73 | 73 + firstname-730 | lastname-730 | 730 + firstname-731 | lastname-731 | 731 + firstname-732 | lastname-732 | 732 + firstname-733 | lastname-733 | 733 + firstname-734 | lastname-734 | 734 + firstname-735 | lastname-735 | 735 + firstname-736 | lastname-736 | 736 + firstname-737 | lastname-737 | 737 + firstname-738 | lastname-738 | 738 + firstname-739 | lastname-739 | 739 + firstname-74 | lastname-74 | 74 + firstname-740 | lastname-740 | 740 + firstname-741 | lastname-741 | 741 + firstname-742 | lastname-742 | 742 + firstname-743 | lastname-743 | 743 + firstname-744 | lastname-744 | 744 + firstname-745 | lastname-745 | 745 + firstname-746 | lastname-746 | 746 + firstname-747 | lastname-747 | 747 + firstname-748 | lastname-748 | 748 + firstname-749 | lastname-749 | 749 + firstname-75 | lastname-75 | 75 + firstname-750 | lastname-750 | 750 + firstname-751 | lastname-751 | 751 + firstname-752 | lastname-752 | 752 + firstname-753 | lastname-753 | 753 + firstname-754 | lastname-754 | 754 + firstname-755 | lastname-755 | 755 + firstname-756 | lastname-756 | 756 + firstname-757 | lastname-757 | 757 + firstname-758 | lastname-758 | 758 + firstname-759 | lastname-759 | 759 + firstname-76 | lastname-76 | 76 + firstname-760 | lastname-760 | 760 + firstname-761 | lastname-761 | 761 + firstname-762 | lastname-762 | 762 + firstname-763 | lastname-763 | 763 + firstname-764 | lastname-764 | 764 + firstname-765 | lastname-765 | 765 + firstname-766 | lastname-766 | 766 + firstname-767 | lastname-767 | 767 + firstname-768 | lastname-768 | 768 + firstname-769 | lastname-769 | 769 + firstname-77 | lastname-77 | 77 + firstname-770 | lastname-770 | 770 + firstname-771 | lastname-771 | 771 + firstname-772 | lastname-772 | 772 + firstname-773 | lastname-773 | 773 + firstname-774 | lastname-774 | 774 + firstname-775 | lastname-775 | 775 + firstname-776 | lastname-776 | 776 + firstname-777 | lastname-777 | 777 + firstname-778 | lastname-778 | 778 + firstname-779 | lastname-779 | 779 + firstname-78 | lastname-78 | 78 + firstname-780 | lastname-780 | 780 + firstname-781 | lastname-781 | 781 + firstname-782 | lastname-782 | 782 + firstname-783 | lastname-783 | 783 + firstname-784 | lastname-784 | 784 + firstname-785 | lastname-785 | 785 + firstname-786 | lastname-786 | 786 + firstname-787 | lastname-787 | 787 + firstname-788 | lastname-788 | 788 + firstname-789 | lastname-789 | 789 + firstname-79 | lastname-79 | 79 + firstname-790 | lastname-790 | 790 + firstname-791 | lastname-791 | 791 + firstname-792 | lastname-792 | 792 + firstname-793 | lastname-793 | 793 + firstname-794 | lastname-794 | 794 + firstname-795 | lastname-795 | 795 + firstname-796 | lastname-796 | 796 + firstname-797 | lastname-797 | 797 + firstname-798 | lastname-798 | 798 + firstname-799 | lastname-799 | 799 + firstname-8 | lastname-8 | 8 + firstname-80 | lastname-80 | 80 + firstname-800 | lastname-800 | 800 + firstname-801 | lastname-801 | 801 + firstname-802 | lastname-802 | 802 + firstname-803 | lastname-803 | 803 + firstname-804 | lastname-804 | 804 + firstname-805 | lastname-805 | 805 + firstname-806 | lastname-806 | 806 + firstname-807 | lastname-807 | 807 + firstname-808 | lastname-808 | 808 + firstname-809 | lastname-809 | 809 + firstname-81 | lastname-81 | 81 + firstname-810 | lastname-810 | 810 + firstname-811 | lastname-811 | 811 + firstname-812 | lastname-812 | 812 + firstname-813 | lastname-813 | 813 + firstname-814 | lastname-814 | 814 + firstname-815 | lastname-815 | 815 + firstname-816 | lastname-816 | 816 + firstname-817 | lastname-817 | 817 + firstname-818 | lastname-818 | 818 + firstname-819 | lastname-819 | 819 + firstname-82 | lastname-82 | 82 + firstname-820 | lastname-820 | 820 + firstname-821 | lastname-821 | 821 + firstname-822 | lastname-822 | 822 + firstname-823 | lastname-823 | 823 + firstname-824 | lastname-824 | 824 + firstname-825 | lastname-825 | 825 + firstname-826 | lastname-826 | 826 + firstname-827 | lastname-827 | 827 + firstname-828 | lastname-828 | 828 + firstname-829 | lastname-829 | 829 + firstname-83 | lastname-83 | 83 + firstname-830 | lastname-830 | 830 + firstname-831 | lastname-831 | 831 + firstname-832 | lastname-832 | 832 + firstname-833 | lastname-833 | 833 + firstname-834 | lastname-834 | 834 + firstname-835 | lastname-835 | 835 + firstname-836 | lastname-836 | 836 + firstname-837 | lastname-837 | 837 + firstname-838 | lastname-838 | 838 + firstname-839 | lastname-839 | 839 + firstname-84 | lastname-84 | 84 + firstname-840 | lastname-840 | 840 + firstname-841 | lastname-841 | 841 + firstname-842 | lastname-842 | 842 + firstname-843 | lastname-843 | 843 + firstname-844 | lastname-844 | 844 + firstname-845 | lastname-845 | 845 + firstname-846 | lastname-846 | 846 + firstname-847 | lastname-847 | 847 + firstname-848 | lastname-848 | 848 + firstname-849 | lastname-849 | 849 + firstname-85 | lastname-85 | 85 + firstname-850 | lastname-850 | 850 + firstname-851 | lastname-851 | 851 + firstname-852 | lastname-852 | 852 + firstname-853 | lastname-853 | 853 + firstname-854 | lastname-854 | 854 + firstname-855 | lastname-855 | 855 + firstname-856 | lastname-856 | 856 + firstname-857 | lastname-857 | 857 + firstname-858 | lastname-858 | 858 + firstname-859 | lastname-859 | 859 + firstname-86 | lastname-86 | 86 + firstname-860 | lastname-860 | 860 + firstname-861 | lastname-861 | 861 + firstname-862 | lastname-862 | 862 + firstname-863 | lastname-863 | 863 + firstname-864 | lastname-864 | 864 + firstname-865 | lastname-865 | 865 + firstname-866 | lastname-866 | 866 + firstname-867 | lastname-867 | 867 + firstname-868 | lastname-868 | 868 + firstname-869 | lastname-869 | 869 + firstname-87 | lastname-87 | 87 + firstname-870 | lastname-870 | 870 + firstname-871 | lastname-871 | 871 + firstname-872 | lastname-872 | 872 + firstname-873 | lastname-873 | 873 + firstname-874 | lastname-874 | 874 + firstname-875 | lastname-875 | 875 + firstname-876 | lastname-876 | 876 + firstname-877 | lastname-877 | 877 + firstname-878 | lastname-878 | 878 + firstname-879 | lastname-879 | 879 + firstname-88 | lastname-88 | 88 + firstname-880 | lastname-880 | 880 + firstname-881 | lastname-881 | 881 + firstname-882 | lastname-882 | 882 + firstname-883 | lastname-883 | 883 + firstname-884 | lastname-884 | 884 + firstname-885 | lastname-885 | 885 + firstname-886 | lastname-886 | 886 + firstname-887 | lastname-887 | 887 + firstname-888 | lastname-888 | 888 + firstname-889 | lastname-889 | 889 + firstname-89 | lastname-89 | 89 + firstname-890 | lastname-890 | 890 + firstname-891 | lastname-891 | 891 + firstname-892 | lastname-892 | 892 + firstname-893 | lastname-893 | 893 + firstname-894 | lastname-894 | 894 + firstname-895 | lastname-895 | 895 + firstname-896 | lastname-896 | 896 + firstname-897 | lastname-897 | 897 + firstname-898 | lastname-898 | 898 + firstname-899 | lastname-899 | 899 + firstname-9 | lastname-9 | 9 + firstname-90 | lastname-90 | 90 + firstname-900 | lastname-900 | 900 + firstname-901 | lastname-901 | 901 + firstname-902 | lastname-902 | 902 + firstname-903 | lastname-903 | 903 + firstname-904 | lastname-904 | 904 + firstname-905 | lastname-905 | 905 + firstname-906 | lastname-906 | 906 + firstname-907 | lastname-907 | 907 + firstname-908 | lastname-908 | 908 + firstname-909 | lastname-909 | 909 + firstname-91 | lastname-91 | 91 + firstname-910 | lastname-910 | 910 + firstname-911 | lastname-911 | 911 + firstname-912 | lastname-912 | 912 + firstname-913 | lastname-913 | 913 + firstname-914 | lastname-914 | 914 + firstname-915 | lastname-915 | 915 + firstname-916 | lastname-916 | 916 + firstname-917 | lastname-917 | 917 + firstname-918 | lastname-918 | 918 + firstname-919 | lastname-919 | 919 + firstname-92 | lastname-92 | 92 + firstname-920 | lastname-920 | 920 + firstname-921 | lastname-921 | 921 + firstname-922 | lastname-922 | 922 + firstname-923 | lastname-923 | 923 + firstname-924 | lastname-924 | 924 + firstname-925 | lastname-925 | 925 + firstname-926 | lastname-926 | 926 + firstname-927 | lastname-927 | 927 + firstname-928 | lastname-928 | 928 + firstname-929 | lastname-929 | 929 + firstname-93 | lastname-93 | 93 + firstname-930 | lastname-930 | 930 + firstname-931 | lastname-931 | 931 + firstname-932 | lastname-932 | 932 + firstname-933 | lastname-933 | 933 + firstname-934 | lastname-934 | 934 + firstname-935 | lastname-935 | 935 + firstname-936 | lastname-936 | 936 + firstname-937 | lastname-937 | 937 + firstname-938 | lastname-938 | 938 + firstname-939 | lastname-939 | 939 + firstname-94 | lastname-94 | 94 + firstname-940 | lastname-940 | 940 + firstname-941 | lastname-941 | 941 + firstname-942 | lastname-942 | 942 + firstname-943 | lastname-943 | 943 + firstname-944 | lastname-944 | 944 + firstname-945 | lastname-945 | 945 + firstname-946 | lastname-946 | 946 + firstname-947 | lastname-947 | 947 + firstname-948 | lastname-948 | 948 + firstname-949 | lastname-949 | 949 + firstname-95 | lastname-95 | 95 + firstname-950 | lastname-950 | 950 + firstname-951 | lastname-951 | 951 + firstname-952 | lastname-952 | 952 + firstname-953 | lastname-953 | 953 + firstname-954 | lastname-954 | 954 + firstname-955 | lastname-955 | 955 + firstname-956 | lastname-956 | 956 + firstname-957 | lastname-957 | 957 + firstname-958 | lastname-958 | 958 + firstname-959 | lastname-959 | 959 + firstname-96 | lastname-96 | 96 + firstname-960 | lastname-960 | 960 + firstname-961 | lastname-961 | 961 + firstname-962 | lastname-962 | 962 + firstname-963 | lastname-963 | 963 + firstname-964 | lastname-964 | 964 + firstname-965 | lastname-965 | 965 + firstname-966 | lastname-966 | 966 + firstname-967 | lastname-967 | 967 + firstname-968 | lastname-968 | 968 + firstname-969 | lastname-969 | 969 + firstname-97 | lastname-97 | 97 + firstname-970 | lastname-970 | 970 + firstname-971 | lastname-971 | 971 + firstname-972 | lastname-972 | 972 + firstname-973 | lastname-973 | 973 + firstname-974 | lastname-974 | 974 + firstname-975 | lastname-975 | 975 + firstname-976 | lastname-976 | 976 + firstname-977 | lastname-977 | 977 + firstname-978 | lastname-978 | 978 + firstname-979 | lastname-979 | 979 + firstname-98 | lastname-98 | 98 + firstname-980 | lastname-980 | 980 + firstname-981 | lastname-981 | 981 + firstname-982 | lastname-982 | 982 + firstname-983 | lastname-983 | 983 + firstname-984 | lastname-984 | 984 + firstname-985 | lastname-985 | 985 + firstname-986 | lastname-986 | 986 + firstname-987 | lastname-987 | 987 + firstname-988 | lastname-988 | 988 + firstname-989 | lastname-989 | 989 + firstname-99 | lastname-99 | 99 + firstname-990 | lastname-990 | 990 + firstname-991 | lastname-991 | 991 + firstname-992 | lastname-992 | 992 + firstname-993 | lastname-993 | 993 + firstname-994 | lastname-994 | 994 + firstname-995 | lastname-995 | 995 + firstname-996 | lastname-996 | 996 + firstname-997 | lastname-997 | 997 + firstname-998 | lastname-998 | 998 + firstname-999 | lastname-999 | 999 +(999 rows) + +-- disable caching +SET columnar.enable_column_cache = 'f'; +CREATE TABLE test_2 ( + value INT, + updated_value INT +) USING columnar; +INSERT INTO test_2 (value) + SELECT generate_series(1, 1000000, 1); +BEGIN; +SELECT SUM(value) + FROM test_2; + sum +-------------- + 500000500000 +(1 row) + +UPDATE test_2 + SET updated_value = value * 2; +SELECT SUM(updated_value) + FROM test_2; + sum +--------------- + 1000001000000 +(1 row) + +DELETE FROM test_2 + WHERE value % 2 = 0; +SELECT SUM(value) + FROM test_2; + sum +-------------- + 250000000000 +(1 row) + +COMMIT; +DROP TABLE test_2; +set columnar.enable_column_cache = 't'; +CREATE TABLE test_2 ( + value INT, + updated_value INT +) USING columnar; +INSERT INTO test_2 (value) + SELECT generate_series(1, 1000000, 1); +BEGIN; +SELECT SUM(value) + FROM test_2; + sum +-------------- + 500000500000 +(1 row) + +UPDATE test_2 + SET updated_value = value * 2; +SELECT SUM(updated_value) + FROM test_2; + sum +--------------- + 1000001000000 +(1 row) + +DELETE FROM test_2 + WHERE value % 2 = 0; +SELECT SUM(value) + FROM test_2; + sum +-------------- + 250000000000 +(1 row) + +COMMIT; +DROP TABLE test_2; +CREATE TABLE t1 (i int) USING columnar; +INSERT INTO t1 SELECT generate_series(1, 1000000, 1); +EXPLAIN SELECT COUNT(*) FROM t1; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Finalize Aggregate (cost=1472.17..1472.18 rows=1 width=8) + -> Gather (cost=1471.54..1472.15 rows=6 width=8) + Workers Planned: 6 + -> Partial Aggregate (cost=471.54..471.55 rows=1 width=8) + -> Parallel Custom Scan (ColumnarScan) on t1 (cost=0.00..54.88 rows=166667 width=0) + Columnar Projected Columns: +(6 rows) + +DROP TABLE t1; diff --git a/columnar/src/test/regress/sql/columnar_aggregates.sql b/columnar/src/test/regress/sql/columnar_aggregates.sql new file mode 100644 index 00000000..6147b77d --- /dev/null +++ b/columnar/src/test/regress/sql/columnar_aggregates.sql @@ -0,0 +1,109 @@ +-- Custom direct aggregates implementation + +-- 1. SMALLINT + +CREATE TABLE t_smallint(a SMALLINT) USING columnar; + +INSERT INTO t_smallint SELECT g % 16384 FROM GENERATE_SERIES(0, 3000000) g; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + +SET columnar.enable_vectorization TO false; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(*), SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_smallint; + +SET columnar.enable_vectorization TO default; + +DROP TABLE t_smallint; + +-- 2. INT + +CREATE TABLE t_int(a INT) USING columnar; + +INSERT INTO t_int SELECT g FROM GENERATE_SERIES(0, 3000000) g; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + +SET columnar.enable_vectorization TO false; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_int; + +SET columnar.enable_vectorization TO default; + +DROP TABLE t_int; + +-- 3. BIGINT + +CREATE TABLE t_bigint(a BIGINT) USING columnar; + +INSERT INTO t_bigint SELECT g FROM GENERATE_SERIES(0, 3000000) g; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + +SET columnar.enable_vectorization TO false; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + +SELECT SUM(a), AVG(a), MIN(a), MAX(a) FROM t_bigint; + +SET columnar.enable_vectorization TO default; + +DROP TABLE t_bigint; + +-- 4. DATE + +CREATE TABLE t_date(a DATE) USING columnar; + +INSERT INTO t_date VALUES ('2000-01-01'), ('2020-01-01'), ('2010-01-01'), ('2000-01-02'); + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + +SELECT MIN(a), MAX(a) FROM t_date; + +SET columnar.enable_vectorization TO false; + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(a), MAX(a) FROM t_date; + +SELECT MIN(a), MAX(a) FROM t_date; + +SET columnar.enable_vectorization TO default; + +DROP TABLE t_date; + +-- Test exception when we fallback to PG aggregator node + +SET client_min_messages TO 'DEBUG1'; + +CREATE TABLE t_mixed(a INT, b BIGINT, c DATE, d TIME) using columnar; + +INSERT INTO t_mixed VALUES (0, 1000, '2000-01-01', '23:50'), (10, 2000, '2010-01-01', '00:50'); + +-- Vectorized aggregate not found (TIME aggregates are not implemented) + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT MIN(d) FROM t_mixed; + +--Unsupported aggregate argument combination. + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT SUM(a + b) FROM t_mixed; + +-- Vectorized Aggregates accepts only non-const values. + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(1) FROM t_mixed; + +-- Vectorized aggregate with DISTINCT not supported. + +EXPLAIN (verbose, costs off, timing off, summary off) SELECT COUNT(DISTINCT a) FROM t_mixed; + +DROP TABLE t_mixed; + +SET client_min_messages TO default; \ No newline at end of file