}
}
+/*
+ * Build an ExprState that calls the given hash function(s) on the given
+ * 'hash_exprs'. When multiple expressions are present, the hash values
+ * returned by each hash function are combined to produce a single hash value.
+ *
+ * desc: tuple descriptor for the to-be-hashed expressions
+ * ops: TupleTableSlotOps for the TupleDesc
+ * hashfunc_oids: Oid for each hash function to call, one for each 'hash_expr'
+ * collations: collation to use when calling the hash function.
+ * hash_expr: list of expressions to hash the value of
+ * opstrict: array corresponding to the 'hashfunc_oids' to store op_strict()
+ * parent: PlanState node that the 'hash_exprs' will be evaluated at
+ * init_value: Normally 0, but can be set to other values to seed the hash
+ * with some other value. Using non-zero is slightly less efficient but can
+ * be useful.
+ * keep_nulls: if true, evaluation of the returned ExprState will abort early
+ * returning NULL if the given hash function is strict and the Datum to hash
+ * is null. When set to false, any NULL input Datums are skipped.
+ */
+ExprState *
+ExecBuildHash32Expr(TupleDesc desc, const TupleTableSlotOps *ops,
+ const Oid *hashfunc_oids, const List *collations,
+ const List *hash_exprs, const bool *opstrict,
+ PlanState *parent, uint32 init_value, bool keep_nulls)
+{
+ ExprState *state = makeNode(ExprState);
+ ExprEvalStep scratch = {0};
+ List *adjust_jumps = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+ intptr_t strict_opcode;
+ intptr_t opcode;
+
+ Assert(list_length(hash_exprs) == list_length(collations));
+
+ state->parent = parent;
+
+ /* Insert setup steps as needed. */
+ ExecCreateExprSetupSteps(state, (Node *) hash_exprs);
+
+ if (init_value == 0)
+ {
+ /*
+ * No initial value, so we can assign the result of the hash function
+ * for the first hash_expr without having to concern ourselves with
+ * combining the result with any initial value.
+ */
+ strict_opcode = EEOP_HASHDATUM_FIRST_STRICT;
+ opcode = EEOP_HASHDATUM_FIRST;
+ }
+ else
+ {
+ /* Set up operation to set the initial value. */
+ scratch.opcode = EEOP_HASHDATUM_SET_INITVAL;
+ scratch.d.hashdatum_initvalue.init_value = UInt32GetDatum(init_value);
+ scratch.resvalue = &state->resvalue;
+ scratch.resnull = &state->resnull;
+
+ ExprEvalPushStep(state, &scratch);
+
+ /*
+ * When using an initial value use the NEXT32/NEXT32_STRICT ops as the
+ * FIRST/FIRST_STRICT ops would overwrite the stored initial value.
+ */
+ strict_opcode = EEOP_HASHDATUM_NEXT32_STRICT;
+ opcode = EEOP_HASHDATUM_NEXT32;
+ }
+
+ forboth(lc, hash_exprs, lc2, collations)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo;
+ int i = foreach_current_index(lc);
+ Oid funcid;
+ Oid inputcollid = lfirst_oid(lc2);
+
+ funcid = hashfunc_oids[i];
+
+ /* Allocate hash function lookup data. */
+ finfo = palloc0(sizeof(FmgrInfo));
+ fcinfo = palloc0(SizeForFunctionCallInfo(1));
+
+ fmgr_info(funcid, finfo);
+
+ /*
+ * Build the steps to evaluate the hash function's argument have it so
+ * the value of that is stored in the 0th argument of the hash func.
+ */
+ ExecInitExprRec(expr,
+ state,
+ &fcinfo->args[0].value,
+ &fcinfo->args[0].isnull);
+
+ scratch.resvalue = &state->resvalue;
+ scratch.resnull = &state->resnull;
+
+ /* Initialize function call parameter structure too */
+ InitFunctionCallInfoData(*fcinfo, finfo, 1, inputcollid, NULL, NULL);
+
+ scratch.d.hashdatum.finfo = finfo;
+ scratch.d.hashdatum.fcinfo_data = fcinfo;
+ scratch.d.hashdatum.fn_addr = finfo->fn_addr;
+
+ scratch.opcode = opstrict[i] && !keep_nulls ? strict_opcode : opcode;
+ scratch.d.hashdatum.jumpdone = -1;
+
+ ExprEvalPushStep(state, &scratch);
+ adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1);
+
+ /*
+ * For subsequent keys we must combine the hash value with the
+ * previous hashes.
+ */
+ strict_opcode = EEOP_HASHDATUM_NEXT32_STRICT;
+ opcode = EEOP_HASHDATUM_NEXT32;
+ }
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->opcode == EEOP_HASHDATUM_FIRST ||
+ as->opcode == EEOP_HASHDATUM_FIRST_STRICT ||
+ as->opcode == EEOP_HASHDATUM_NEXT32 ||
+ as->opcode == EEOP_HASHDATUM_NEXT32_STRICT);
+ Assert(as->d.hashdatum.jumpdone == -1);
+ as->d.hashdatum.jumpdone = state->steps_len;
+ }
+
+ scratch.resvalue = NULL;
+ scratch.resnull = NULL;
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* Build equality expression that can be evaluated using ExecQual(), returning
* true if the expression context's inner/outer tuple are NOT DISTINCT. I.e
&&CASE_EEOP_DOMAIN_TESTVAL,
&&CASE_EEOP_DOMAIN_NOTNULL,
&&CASE_EEOP_DOMAIN_CHECK,
+ &&CASE_EEOP_HASHDATUM_SET_INITVAL,
+ &&CASE_EEOP_HASHDATUM_FIRST,
+ &&CASE_EEOP_HASHDATUM_FIRST_STRICT,
+ &&CASE_EEOP_HASHDATUM_NEXT32,
+ &&CASE_EEOP_HASHDATUM_NEXT32_STRICT,
&&CASE_EEOP_CONVERT_ROWTYPE,
&&CASE_EEOP_SCALARARRAYOP,
&&CASE_EEOP_HASHED_SCALARARRAYOP,
EEO_NEXT();
}
+ EEO_CASE(EEOP_HASHDATUM_SET_INITVAL)
+ {
+ *op->resvalue = op->d.hashdatum_initvalue.init_value;
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_HASHDATUM_FIRST)
+ {
+ FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
+
+ /*
+ * Save the Datum on non-null inputs, otherwise store 0 so that
+ * subsequent NEXT32 operations combine with an initialized value.
+ */
+ if (!fcinfo->args[0].isnull)
+ *op->resvalue = op->d.hashdatum.fn_addr(fcinfo);
+ else
+ *op->resvalue = (Datum) 0;
+
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_HASHDATUM_FIRST_STRICT)
+ {
+ FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
+
+ if (fcinfo->args[0].isnull)
+ {
+ /*
+ * With strict we have the expression return NULL instead of
+ * ignoring NULL input values. We've nothing more to do after
+ * finding a NULL.
+ */
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
+ EEO_JUMP(op->d.hashdatum.jumpdone);
+ }
+
+ /* execute the hash function and save the resulting value */
+ *op->resvalue = op->d.hashdatum.fn_addr(fcinfo);
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_HASHDATUM_NEXT32)
+ {
+ FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
+ uint32 existing_hash = DatumGetUInt32(*op->resvalue);
+
+ /* combine successive hash values by rotating */
+ existing_hash = pg_rotate_left32(existing_hash, 1);
+
+ /* leave the hash value alone on NULL inputs */
+ if (!fcinfo->args[0].isnull)
+ {
+ uint32 hashvalue;
+
+ /* execute hash func and combine with previous hash value */
+ hashvalue = DatumGetUInt32(op->d.hashdatum.fn_addr(fcinfo));
+ existing_hash = existing_hash ^ hashvalue;
+ }
+
+ *op->resvalue = UInt32GetDatum(existing_hash);
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_HASHDATUM_NEXT32_STRICT)
+ {
+ FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
+
+ if (fcinfo->args[0].isnull)
+ {
+ /*
+ * With strict we have the expression return NULL instead of
+ * ignoring NULL input values. We've nothing more to do after
+ * finding a NULL.
+ */
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
+ EEO_JUMP(op->d.hashdatum.jumpdone);
+ }
+ else
+ {
+ uint32 existing_hash = DatumGetUInt32(*op->resvalue);
+ uint32 hashvalue;
+
+ /* combine successive hash values by rotating */
+ existing_hash = pg_rotate_left32(existing_hash, 1);
+
+ /* execute hash func and combine with previous hash value */
+ hashvalue = DatumGetUInt32(op->d.hashdatum.fn_addr(fcinfo));
+ *op->resvalue = UInt32GetDatum(existing_hash ^ hashvalue);
+ *op->resnull = false;
+ }
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_XMLEXPR)
{
/* too complex for an inline implementation */
static void ExecHashIncreaseNumBuckets(HashJoinTable hashtable);
static void ExecParallelHashIncreaseNumBatches(HashJoinTable hashtable);
static void ExecParallelHashIncreaseNumBuckets(HashJoinTable hashtable);
-static void ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node,
+static void ExecHashBuildSkewHash(HashState *hashstate,
+ HashJoinTable hashtable, Hash *node,
int mcvsToUse);
static void ExecHashSkewTableInsert(HashJoinTable hashtable,
TupleTableSlot *slot,
MultiExecPrivateHash(HashState *node)
{
PlanState *outerNode;
- List *hashkeys;
HashJoinTable hashtable;
TupleTableSlot *slot;
ExprContext *econtext;
- uint32 hashvalue;
/*
* get state info from node
/*
* set expression context
*/
- hashkeys = node->hashkeys;
econtext = node->ps.ps_ExprContext;
/*
*/
for (;;)
{
+ bool isnull;
+ Datum hashdatum;
+
slot = ExecProcNode(outerNode);
if (TupIsNull(slot))
break;
/* We have to compute the hash value */
econtext->ecxt_outertuple = slot;
- if (ExecHashGetHashValue(hashtable, econtext, hashkeys,
- false, hashtable->keepNulls,
- &hashvalue))
+
+ ResetExprContext(econtext);
+
+ hashdatum = ExecEvalExprSwitchContext(node->hash_expr, econtext,
+ &isnull);
+
+ if (!isnull)
{
+ uint32 hashvalue = DatumGetUInt32(hashdatum);
int bucketNumber;
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue);
{
ParallelHashJoinState *pstate;
PlanState *outerNode;
- List *hashkeys;
HashJoinTable hashtable;
TupleTableSlot *slot;
ExprContext *econtext;
/*
* set expression context
*/
- hashkeys = node->hashkeys;
econtext = node->ps.ps_ExprContext;
/*
ExecParallelHashTableSetCurrentBatch(hashtable, 0);
for (;;)
{
+ bool isnull;
+
slot = ExecProcNode(outerNode);
if (TupIsNull(slot))
break;
econtext->ecxt_outertuple = slot;
- if (ExecHashGetHashValue(hashtable, econtext, hashkeys,
- false, hashtable->keepNulls,
- &hashvalue))
+
+ ResetExprContext(econtext);
+
+ hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(node->hash_expr,
+ econtext,
+ &isnull));
+
+ if (!isnull)
ExecParallelHashTableInsert(hashtable, slot, hashvalue);
hashtable->partialTuples++;
}
hashstate->ps.plan = (Plan *) node;
hashstate->ps.state = estate;
hashstate->ps.ExecProcNode = ExecHash;
+ /* delay building hashtable until ExecHashTableCreate() in executor run */
hashstate->hashtable = NULL;
- hashstate->hashkeys = NIL; /* will be set by parent HashJoin */
/*
* Miscellaneous initialization
ExecInitResultTupleSlotTL(&hashstate->ps, &TTSOpsMinimalTuple);
hashstate->ps.ps_ProjInfo = NULL;
+ Assert(node->plan.qual == NIL);
+
/*
- * initialize child expressions
+ * Delay initialization of hash_expr until ExecInitHashJoin(). We cannot
+ * build the ExprState here as we don't yet know the join type we're going
+ * to be hashing values for and we need to know that before calling
+ * ExecBuildHash32Expr as the keep_nulls parameter depends on the join
+ * type.
*/
- Assert(node->plan.qual == NIL);
- hashstate->hashkeys =
- ExecInitExprList(node->hashkeys, (PlanState *) hashstate);
+ hashstate->hash_expr = NULL;
return hashstate;
}
* ----------------------------------------------------------------
*/
HashJoinTable
-ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, bool keepNulls)
+ExecHashTableCreate(HashState *state)
{
Hash *node;
HashJoinTable hashtable;
double rows;
int num_skew_mcvs;
int log2_nbuckets;
- int nkeys;
- int i;
- ListCell *ho;
- ListCell *hc;
MemoryContext oldcxt;
/*
hashtable->log2_nbuckets = log2_nbuckets;
hashtable->log2_nbuckets_optimal = log2_nbuckets;
hashtable->buckets.unshared = NULL;
- hashtable->keepNulls = keepNulls;
hashtable->skewEnabled = false;
hashtable->skewBucket = NULL;
hashtable->skewBucketLen = 0;
oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
- /*
- * Get info about the hash functions to be used for each hash key. Also
- * remember whether the join operators are strict.
- */
- nkeys = list_length(hashOperators);
- hashtable->outer_hashfunctions = palloc_array(FmgrInfo, nkeys);
- hashtable->inner_hashfunctions = palloc_array(FmgrInfo, nkeys);
- hashtable->hashStrict = palloc_array(bool, nkeys);
- hashtable->collations = palloc_array(Oid, nkeys);
- i = 0;
- forboth(ho, hashOperators, hc, hashCollations)
- {
- Oid hashop = lfirst_oid(ho);
- Oid left_hashfn;
- Oid right_hashfn;
-
- if (!get_op_hash_functions(hashop, &left_hashfn, &right_hashfn))
- elog(ERROR, "could not find hash function for hash operator %u",
- hashop);
- fmgr_info(left_hashfn, &hashtable->outer_hashfunctions[i]);
- fmgr_info(right_hashfn, &hashtable->inner_hashfunctions[i]);
- hashtable->hashStrict[i] = op_strict(hashop);
- hashtable->collations[i] = lfirst_oid(hc);
- i++;
- }
-
if (nbatch > 1 && hashtable->parallel_state == NULL)
{
MemoryContext oldctx;
* it.)
*/
if (nbatch > 1)
- ExecHashBuildSkewHash(hashtable, node, num_skew_mcvs);
+ ExecHashBuildSkewHash(state, hashtable, node, num_skew_mcvs);
MemoryContextSwitchTo(oldcxt);
}
heap_free_minimal_tuple(tuple);
}
-/*
- * ExecHashGetHashValue
- * Compute the hash value for a tuple
- *
- * The tuple to be tested must be in econtext->ecxt_outertuple (thus Vars in
- * the hashkeys expressions need to have OUTER_VAR as varno). If outer_tuple
- * is false (meaning it's the HashJoin's inner node, Hash), econtext,
- * hashkeys, and slot need to be from Hash, with hashkeys/slot referencing and
- * being suitable for tuples from the node below the Hash. Conversely, if
- * outer_tuple is true, econtext is from HashJoin, and hashkeys/slot need to
- * be appropriate for tuples from HashJoin's outer node.
- *
- * A true result means the tuple's hash value has been successfully computed
- * and stored at *hashvalue. A false result means the tuple cannot match
- * because it contains a null attribute, and hence it should be discarded
- * immediately. (If keep_nulls is true then false is never returned.)
- */
-bool
-ExecHashGetHashValue(HashJoinTable hashtable,
- ExprContext *econtext,
- List *hashkeys,
- bool outer_tuple,
- bool keep_nulls,
- uint32 *hashvalue)
-{
- uint32 hashkey = 0;
- FmgrInfo *hashfunctions;
- ListCell *hk;
- int i = 0;
- MemoryContext oldContext;
-
- /*
- * We reset the eval context each time to reclaim any memory leaked in the
- * hashkey expressions.
- */
- ResetExprContext(econtext);
-
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
- if (outer_tuple)
- hashfunctions = hashtable->outer_hashfunctions;
- else
- hashfunctions = hashtable->inner_hashfunctions;
-
- foreach(hk, hashkeys)
- {
- ExprState *keyexpr = (ExprState *) lfirst(hk);
- Datum keyval;
- bool isNull;
-
- /* combine successive hashkeys by rotating */
- hashkey = pg_rotate_left32(hashkey, 1);
-
- /*
- * Get the join attribute value of the tuple
- */
- keyval = ExecEvalExpr(keyexpr, econtext, &isNull);
-
- /*
- * If the attribute is NULL, and the join operator is strict, then
- * this tuple cannot pass the join qual so we can reject it
- * immediately (unless we're scanning the outside of an outer join, in
- * which case we must not reject it). Otherwise we act like the
- * hashcode of NULL is zero (this will support operators that act like
- * IS NOT DISTINCT, though not any more-random behavior). We treat
- * the hash support function as strict even if the operator is not.
- *
- * Note: currently, all hashjoinable operators must be strict since
- * the hash index AM assumes that. However, it takes so little extra
- * code here to allow non-strict that we may as well do it.
- */
- if (isNull)
- {
- if (hashtable->hashStrict[i] && !keep_nulls)
- {
- MemoryContextSwitchTo(oldContext);
- return false; /* cannot match */
- }
- /* else, leave hashkey unmodified, equivalent to hashcode 0 */
- }
- else
- {
- /* Compute the hash function */
- uint32 hkey;
-
- hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i], hashtable->collations[i], keyval));
- hashkey ^= hkey;
- }
-
- i++;
- }
-
- MemoryContextSwitchTo(oldContext);
-
- *hashvalue = hashkey;
- return true;
-}
/*
* ExecHashGetBucketAndBatch
* based on available memory.
*/
static void
-ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse)
+ExecHashBuildSkewHash(HashState *hashstate, HashJoinTable hashtable,
+ Hash *node, int mcvsToUse)
{
HeapTupleData *statsTuple;
AttStatsSlot sslot;
{
double frac;
int nbuckets;
- FmgrInfo *hashfunctions;
int i;
if (mcvsToUse > sslot.nvalues)
* ExecHashRemoveNextSkewBucket) and we want the least common MCVs to
* be removed first.
*/
- hashfunctions = hashtable->outer_hashfunctions;
for (i = 0; i < mcvsToUse; i++)
{
uint32 hashvalue;
int bucket;
- hashvalue = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[0],
- hashtable->collations[0],
+ hashvalue = DatumGetUInt32(FunctionCall1Coll(hashstate->skew_hashfunction,
+ hashstate->skew_collation,
sslot.values[i]));
/*
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
#include "miscadmin.h"
+#include "utils/lsyscache.h"
#include "utils/sharedtuplestore.h"
#include "utils/wait_event.h"
* whoever gets here first will create the hash table and any
* later arrivals will merely attach to it.
*/
- hashtable = ExecHashTableCreate(hashNode,
- node->hj_HashOperators,
- node->hj_Collations,
- HJ_FILL_INNER(node));
+ hashtable = ExecHashTableCreate(hashNode);
node->hj_HashTable = hashtable;
/*
*/
{
HashState *hashstate = (HashState *) innerPlanState(hjstate);
+ Hash *hash = (Hash *) hashstate->ps.plan;
TupleTableSlot *slot = hashstate->ps.ps_ResultTupleSlot;
+ Oid *outer_hashfuncid;
+ Oid *inner_hashfuncid;
+ bool *hash_strict;
+ ListCell *lc;
+ int nkeys;
+
hjstate->hj_HashTupleSlot = slot;
+
+ /*
+ * Build ExprStates to obtain hash values for either side of the join.
+ * This must be done here as ExecBuildHash32Expr needs to know how to
+ * handle NULL inputs and the required handling of that depends on the
+ * jointype. We don't know the join type in ExecInitHash() and we
+ * must build the ExprStates before ExecHashTableCreate() so we
+ * properly attribute any SubPlans that exist in the hash expressions
+ * to the correct PlanState.
+ */
+ nkeys = list_length(node->hashoperators);
+
+ outer_hashfuncid = palloc_array(Oid, nkeys);
+ inner_hashfuncid = palloc_array(Oid, nkeys);
+ hash_strict = palloc_array(bool, nkeys);
+
+ /*
+ * Determine the hash function for each side of the join for the given
+ * hash operator.
+ */
+ foreach(lc, node->hashoperators)
+ {
+ Oid hashop = lfirst_oid(lc);
+ int i = foreach_current_index(lc);
+
+ if (!get_op_hash_functions(hashop,
+ &outer_hashfuncid[i],
+ &inner_hashfuncid[i]))
+ elog(ERROR,
+ "could not find hash function for hash operator %u",
+ hashop);
+ hash_strict[i] = op_strict(hashop);
+ }
+
+ /*
+ * Build an ExprState to generate the hash value for the expressions
+ * on the outer of the join. This ExprState must finish generating
+ * the hash value when HJ_FILL_OUTER() is true. Otherwise,
+ * ExecBuildHash32Expr will set up the ExprState to abort early if it
+ * finds a NULL. In these cases, we don't need to store these tuples
+ * in the hash table as the jointype does not require it.
+ */
+ hjstate->hj_OuterHash =
+ ExecBuildHash32Expr(hjstate->js.ps.ps_ResultTupleDesc,
+ hjstate->js.ps.resultops,
+ outer_hashfuncid,
+ node->hashcollations,
+ node->hashkeys,
+ hash_strict,
+ &hjstate->js.ps,
+ 0,
+ HJ_FILL_OUTER(hjstate));
+
+ /* As above, but for the inner side of the join */
+ hashstate->hash_expr =
+ ExecBuildHash32Expr(hashstate->ps.ps_ResultTupleDesc,
+ hashstate->ps.resultops,
+ inner_hashfuncid,
+ node->hashcollations,
+ hash->hashkeys,
+ hash_strict,
+ &hashstate->ps,
+ 0,
+ HJ_FILL_INNER(hjstate));
+
+ /*
+ * Set up the skew table hash function while we have a record of the
+ * first key's hash function Oid.
+ */
+ if (OidIsValid(hash->skewTable))
+ {
+ hashstate->skew_hashfunction = palloc0(sizeof(FmgrInfo));
+ hashstate->skew_collation = linitial_oid(node->hashcollations);
+ fmgr_info(outer_hashfuncid[0], hashstate->skew_hashfunction);
+ }
+
+ /* no need to keep these */
+ pfree(outer_hashfuncid);
+ pfree(inner_hashfuncid);
+ pfree(hash_strict);
}
/*
hjstate->hj_CurSkewBucketNo = INVALID_SKEW_BUCKET_NO;
hjstate->hj_CurTuple = NULL;
- hjstate->hj_OuterHashKeys = ExecInitExprList(node->hashkeys,
- (PlanState *) hjstate);
- hjstate->hj_HashOperators = node->hashoperators;
- hjstate->hj_Collations = node->hashcollations;
-
hjstate->hj_JoinState = HJ_BUILD_HASHTABLE;
hjstate->hj_MatchedOuter = false;
hjstate->hj_OuterNotEmpty = false;
while (!TupIsNull(slot))
{
+ bool isnull;
+
/*
* We have to compute the tuple's hash value.
*/
ExprContext *econtext = hjstate->js.ps.ps_ExprContext;
econtext->ecxt_outertuple = slot;
- if (ExecHashGetHashValue(hashtable, econtext,
- hjstate->hj_OuterHashKeys,
- true, /* outer tuple */
- HJ_FILL_OUTER(hjstate),
- hashvalue))
+
+ ResetExprContext(econtext);
+
+ *hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash,
+ econtext,
+ &isnull));
+
+ if (!isnull)
{
/* remember outer relation is not empty for possible rescan */
hjstate->hj_OuterNotEmpty = true;
while (!TupIsNull(slot))
{
+ bool isnull;
+
ExprContext *econtext = hjstate->js.ps.ps_ExprContext;
econtext->ecxt_outertuple = slot;
- if (ExecHashGetHashValue(hashtable, econtext,
- hjstate->hj_OuterHashKeys,
- true, /* outer tuple */
- HJ_FILL_OUTER(hjstate),
- hashvalue))
+
+ ResetExprContext(econtext);
+
+ *hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash,
+ econtext,
+ &isnull));
+
+ if (!isnull)
return slot;
/*
/* Execute outer plan, writing all tuples to shared tuplestores. */
for (;;)
{
+ bool isnull;
+
slot = ExecProcNode(outerState);
if (TupIsNull(slot))
break;
econtext->ecxt_outertuple = slot;
- if (ExecHashGetHashValue(hashtable, econtext,
- hjstate->hj_OuterHashKeys,
- true, /* outer tuple */
- HJ_FILL_OUTER(hjstate),
- &hashvalue))
+
+ ResetExprContext(econtext);
+
+ hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash,
+ econtext,
+ &isnull));
+
+ if (!isnull)
{
int batchno;
int bucketno;
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_HASHDATUM_SET_INITVAL:
+ {
+ LLVMValueRef v_initvalue;
+
+ v_initvalue = l_sizet_const(op->d.hashdatum_initvalue.init_value);
+
+ LLVMBuildStore(b, v_initvalue, v_resvaluep);
+ LLVMBuildStore(b, l_sbool_const(0), v_resnullp);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+ }
+
+ case EEOP_HASHDATUM_FIRST:
+ case EEOP_HASHDATUM_FIRST_STRICT:
+ case EEOP_HASHDATUM_NEXT32:
+ case EEOP_HASHDATUM_NEXT32_STRICT:
+ {
+ FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
+ LLVMValueRef v_fcinfo;
+ LLVMValueRef v_fcinfo_isnull;
+ LLVMValueRef v_retval;
+ LLVMBasicBlockRef b_checkargnull;
+ LLVMBasicBlockRef b_ifnotnull;
+ LLVMBasicBlockRef b_ifnullblock;
+ LLVMValueRef v_argisnull;
+ LLVMValueRef v_prevhash = NULL;
+
+ /*
+ * When performing the next hash and not in strict mode we
+ * perform a rotation of the previously stored hash value
+ * before doing the NULL check. We want to do this even
+ * when we receive a NULL Datum to hash. In strict mode,
+ * we do this after the NULL check so as not to waste the
+ * effort of rotating the bits when we're going to throw
+ * away the hash value and return NULL.
+ */
+ if (opcode == EEOP_HASHDATUM_NEXT32)
+ {
+ LLVMValueRef v_tmp1;
+ LLVMValueRef v_tmp2;
+
+ /*
+ * Fetch the previously hashed value from where the
+ * EEOP_HASHDATUM_FIRST operation stored it.
+ */
+ v_prevhash = l_load(b, TypeSizeT, v_resvaluep,
+ "prevhash");
+
+ /*
+ * Rotate bits left by 1 bit. Be careful not to
+ * overflow uint32 when working with size_t.
+ */
+ v_tmp1 = LLVMBuildShl(b, v_prevhash, l_sizet_const(1),
+ "");
+ v_tmp1 = LLVMBuildAnd(b, v_tmp1,
+ l_sizet_const(0xffffffff), "");
+ v_tmp2 = LLVMBuildLShr(b, v_prevhash,
+ l_sizet_const(31), "");
+ v_prevhash = LLVMBuildOr(b, v_tmp1, v_tmp2,
+ "rotatedhash");
+ }
+
+ /*
+ * Block for the actual function call, if args are
+ * non-NULL.
+ */
+ b_ifnotnull = l_bb_before_v(opblocks[opno + 1],
+ "b.%d.ifnotnull",
+ opno);
+
+ /* we expect the hash function to have 1 argument */
+ if (fcinfo->nargs != 1)
+ elog(ERROR, "incorrect number of function arguments");
+
+ v_fcinfo = l_ptr_const(fcinfo,
+ l_ptr(StructFunctionCallInfoData));
+
+ b_checkargnull = l_bb_before_v(b_ifnotnull,
+ "b.%d.isnull.0", opno);
+
+ LLVMBuildBr(b, b_checkargnull);
+
+ /*
+ * Determine what to do if we find the argument to be
+ * NULL.
+ */
+ if (opcode == EEOP_HASHDATUM_FIRST_STRICT ||
+ opcode == EEOP_HASHDATUM_NEXT32_STRICT)
+ {
+ b_ifnullblock = l_bb_before_v(b_ifnotnull,
+ "b.%d.strictnull",
+ opno);
+
+ LLVMPositionBuilderAtEnd(b, b_ifnullblock);
+
+ /*
+ * In strict node, NULL inputs result in NULL. Save
+ * the NULL result and goto jumpdone.
+ */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ LLVMBuildStore(b, l_sizet_const(0), v_resvaluep);
+ LLVMBuildBr(b, opblocks[op->d.hashdatum.jumpdone]);
+ }
+ else
+ {
+ b_ifnullblock = l_bb_before_v(b_ifnotnull,
+ "b.%d.null",
+ opno);
+
+ LLVMPositionBuilderAtEnd(b, b_ifnullblock);
+
+
+ LLVMBuildStore(b, l_sbool_const(0), v_resnullp);
+
+ if (opcode == EEOP_HASHDATUM_NEXT32)
+ {
+ Assert(v_prevhash != NULL);
+
+ /*
+ * Save the rotated hash value and skip to the
+ * next op.
+ */
+ LLVMBuildStore(b, v_prevhash, v_resvaluep);
+ }
+ else
+ {
+ Assert(opcode == EEOP_HASHDATUM_FIRST);
+
+ /*
+ * Store a zero Datum when the Datum to hash is
+ * NULL
+ */
+ LLVMBuildStore(b, l_sizet_const(0), v_resvaluep);
+ }
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+
+ LLVMPositionBuilderAtEnd(b, b_checkargnull);
+
+ /* emit code to check if the input parameter is NULL */
+ v_argisnull = l_funcnull(b, v_fcinfo, 0);
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b,
+ LLVMIntEQ,
+ v_argisnull,
+ l_sbool_const(1),
+ ""),
+ b_ifnullblock,
+ b_ifnotnull);
+
+ LLVMPositionBuilderAtEnd(b, b_ifnotnull);
+
+ /*
+ * Rotate the previously stored hash value when performing
+ * NEXT32 in strict mode. In non-strict mode we already
+ * did this before checking for NULLs.
+ */
+ if (opcode == EEOP_HASHDATUM_NEXT32_STRICT)
+ {
+ LLVMValueRef v_tmp1;
+ LLVMValueRef v_tmp2;
+
+ /*
+ * Fetch the previously hashed value from where the
+ * EEOP_HASHDATUM_FIRST_STRICT operation stored it.
+ */
+ v_prevhash = l_load(b, TypeSizeT, v_resvaluep,
+ "prevhash");
+
+ /*
+ * Rotate bits left by 1 bit. Be careful not to
+ * overflow uint32 when working with size_t.
+ */
+ v_tmp1 = LLVMBuildShl(b, v_prevhash, l_sizet_const(1),
+ "");
+ v_tmp1 = LLVMBuildAnd(b, v_tmp1,
+ l_sizet_const(0xffffffff), "");
+ v_tmp2 = LLVMBuildLShr(b, v_prevhash,
+ l_sizet_const(31), "");
+ v_prevhash = LLVMBuildOr(b, v_tmp1, v_tmp2,
+ "rotatedhash");
+ }
+
+ /* call the hash function */
+ v_retval = BuildV1Call(context, b, mod, fcinfo,
+ &v_fcinfo_isnull);
+
+ /*
+ * For NEXT32 ops, XOR (^) the returned hash value with
+ * the existing hash value.
+ */
+ if (opcode == EEOP_HASHDATUM_NEXT32 ||
+ opcode == EEOP_HASHDATUM_NEXT32_STRICT)
+ v_retval = LLVMBuildXor(b, v_prevhash, v_retval,
+ "xorhash");
+
+ LLVMBuildStore(b, v_retval, v_resvaluep);
+ LLVMBuildStore(b, l_sbool_const(0), v_resnullp);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+ }
+
case EEOP_CONVERT_ROWTYPE:
build_EvalXFunc(b, mod, "ExecEvalConvertRowtype",
v_state, op, v_econtext);
/* evaluate a single domain CHECK constraint */
EEOP_DOMAIN_CHECK,
+ /* evaluation steps for hashing */
+ EEOP_HASHDATUM_SET_INITVAL,
+ EEOP_HASHDATUM_FIRST,
+ EEOP_HASHDATUM_FIRST_STRICT,
+ EEOP_HASHDATUM_NEXT32,
+ EEOP_HASHDATUM_NEXT32_STRICT,
+
/* evaluate assorted special-purpose expression types */
EEOP_CONVERT_ROWTYPE,
EEOP_SCALARARRAYOP,
ErrorSaveContext *escontext;
} domaincheck;
+ /* for EEOP_HASH_SET_INITVAL */
+ struct
+ {
+ Datum init_value;
+
+ } hashdatum_initvalue;
+
+ /* for EEOP_HASHDATUM_(FIRST|NEXT32)[_STRICT] */
+ struct
+ {
+ FmgrInfo *finfo; /* function's lookup data */
+ FunctionCallInfo fcinfo_data; /* arguments etc */
+ /* faster to access without additional indirection: */
+ PGFunction fn_addr; /* actual call address */
+ int jumpdone; /* jump here on null */
+ } hashdatum;
+
/* for EEOP_CONVERT_ROWTYPE */
struct
{
extern List *ExecInitExprList(List *nodes, PlanState *parent);
extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase,
bool doSort, bool doHash, bool nullcheck);
+extern ExprState *ExecBuildHash32Expr(TupleDesc desc,
+ const TupleTableSlotOps *ops,
+ const Oid *hashfunc_oids,
+ const List *collations,
+ const List *hash_exprs,
+ const bool *opstrict, PlanState *parent,
+ uint32 init_value, bool keep_nulls);
extern ExprState *ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
const TupleTableSlotOps *lops, const TupleTableSlotOps *rops,
int numCols,
dsa_pointer_atomic *shared;
} buckets;
- bool keepNulls; /* true to store unmatchable NULL tuples */
-
bool skewEnabled; /* are we using skew optimization? */
HashSkewBucket **skewBucket; /* hashtable of skew buckets */
int skewBucketLen; /* size of skewBucket array (a power of 2!) */
BufFile **innerBatchFile; /* buffered virtual temp file per batch */
BufFile **outerBatchFile; /* buffered virtual temp file per batch */
- /*
- * Info about the datatype-specific hash functions for the datatypes being
- * hashed. These are arrays of the same length as the number of hash join
- * clauses (hash keys).
- */
- FmgrInfo *outer_hashfunctions; /* lookup data for hash functions */
- FmgrInfo *inner_hashfunctions; /* lookup data for hash functions */
- bool *hashStrict; /* is each hash join operator strict? */
- Oid *collations;
-
Size spaceUsed; /* memory space currently used by tuples */
Size spaceAllowed; /* upper limit for space used */
Size spacePeak; /* peak space used */
extern void ExecEndHash(HashState *node);
extern void ExecReScanHash(HashState *node);
-extern HashJoinTable ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
- bool keepNulls);
+extern HashJoinTable ExecHashTableCreate(HashState *state);
extern void ExecParallelHashTableAlloc(HashJoinTable hashtable,
int batchno);
extern void ExecHashTableDestroy(HashJoinTable hashtable);
extern void ExecParallelHashTableInsertCurrentBatch(HashJoinTable hashtable,
TupleTableSlot *slot,
uint32 hashvalue);
-extern bool ExecHashGetHashValue(HashJoinTable hashtable,
- ExprContext *econtext,
- List *hashkeys,
- bool outer_tuple,
- bool keep_nulls,
- uint32 *hashvalue);
extern void ExecHashGetBucketAndBatch(HashJoinTable hashtable,
uint32 hashvalue,
int *bucketno,
* HashJoinState information
*
* hashclauses original form of the hashjoin condition
- * hj_OuterHashKeys the outer hash keys in the hashjoin condition
- * hj_HashOperators the join operators in the hashjoin condition
+ * hj_OuterHash ExprState for hashing outer keys
* hj_HashTable hash table for the hashjoin
* (NULL if table not built yet)
* hj_CurHashValue hash value for current outer tuple
{
JoinState js; /* its first field is NodeTag */
ExprState *hashclauses;
- List *hj_OuterHashKeys; /* list of ExprState nodes */
- List *hj_HashOperators; /* list of operator OIDs */
- List *hj_Collations;
+ ExprState *hj_OuterHash;
HashJoinTable hj_HashTable;
uint32 hj_CurHashValue;
int hj_CurBucketNo;
{
PlanState ps; /* its first field is NodeTag */
HashJoinTable hashtable; /* hash table for the hashjoin */
- List *hashkeys; /* list of ExprState nodes */
+ ExprState *hash_expr; /* ExprState to get hash value */
+
+ FmgrInfo *skew_hashfunction; /* lookup data for skew hash function */
+ Oid skew_collation; /* collation to call skew_hashfunction with */
/*
* In a parallelized hash join, the leader retains a pointer to the