Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * execGrouping.c
4 : * executor utility routines for grouping, hashing, and aggregation
5 : *
6 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/executor/execGrouping.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include <math.h>
18 :
19 : #include "access/htup_details.h"
20 : #include "access/parallel.h"
21 : #include "common/hashfn.h"
22 : #include "executor/executor.h"
23 : #include "miscadmin.h"
24 : #include "utils/lsyscache.h"
25 :
26 : static int TupleHashTableMatch(struct tuplehash_hash *tb, MinimalTuple tuple1, MinimalTuple tuple2);
27 : static inline uint32 TupleHashTableHash_internal(struct tuplehash_hash *tb,
28 : MinimalTuple tuple);
29 : static inline TupleHashEntry LookupTupleHashEntry_internal(TupleHashTable hashtable,
30 : TupleTableSlot *slot,
31 : bool *isnew, uint32 hash);
32 :
33 : /*
34 : * Define parameters for tuple hash table code generation. The interface is
35 : * *also* declared in execnodes.h (to generate the types, which are externally
36 : * visible).
37 : */
38 : #define SH_PREFIX tuplehash
39 : #define SH_ELEMENT_TYPE TupleHashEntryData
40 : #define SH_KEY_TYPE MinimalTuple
41 : #define SH_KEY firstTuple
42 : #define SH_HASH_KEY(tb, key) TupleHashTableHash_internal(tb, key)
43 : #define SH_EQUAL(tb, a, b) TupleHashTableMatch(tb, a, b) == 0
44 : #define SH_SCOPE extern
45 : #define SH_STORE_HASH
46 : #define SH_GET_HASH(tb, a) a->hash
47 : #define SH_DEFINE
48 : #include "lib/simplehash.h"
49 :
50 :
51 : /*****************************************************************************
52 : * Utility routines for grouping tuples together
53 : *****************************************************************************/
54 :
55 : /*
56 : * execTuplesMatchPrepare
57 : * Build expression that can be evaluated using ExecQual(), returning
58 : * whether an ExprContext's inner/outer tuples are NOT DISTINCT
59 : */
60 : ExprState *
61 11834 : execTuplesMatchPrepare(TupleDesc desc,
62 : int numCols,
63 : const AttrNumber *keyColIdx,
64 : const Oid *eqOperators,
65 : const Oid *collations,
66 : PlanState *parent)
67 : {
68 : Oid *eqFunctions;
69 : int i;
70 : ExprState *expr;
71 :
72 11834 : if (numCols == 0)
73 54 : return NULL;
74 :
75 11780 : eqFunctions = (Oid *) palloc(numCols * sizeof(Oid));
76 :
77 : /* lookup equality functions */
78 31962 : for (i = 0; i < numCols; i++)
79 20182 : eqFunctions[i] = get_opcode(eqOperators[i]);
80 :
81 : /* build actual expression */
82 11780 : expr = ExecBuildGroupingEqual(desc, desc, NULL, NULL,
83 : numCols, keyColIdx, eqFunctions, collations,
84 : parent);
85 :
86 11780 : return expr;
87 : }
88 :
89 : /*
90 : * execTuplesHashPrepare
91 : * Look up the equality and hashing functions needed for a TupleHashTable.
92 : *
93 : * This is similar to execTuplesMatchPrepare, but we also need to find the
94 : * hash functions associated with the equality operators. *eqFunctions and
95 : * *hashFunctions receive the palloc'd result arrays.
96 : *
97 : * Note: we expect that the given operators are not cross-type comparisons.
98 : */
99 : void
100 8300 : execTuplesHashPrepare(int numCols,
101 : const Oid *eqOperators,
102 : Oid **eqFuncOids,
103 : FmgrInfo **hashFunctions)
104 : {
105 : int i;
106 :
107 8300 : *eqFuncOids = (Oid *) palloc(numCols * sizeof(Oid));
108 8300 : *hashFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
109 :
110 21950 : for (i = 0; i < numCols; i++)
111 : {
112 13650 : Oid eq_opr = eqOperators[i];
113 : Oid eq_function;
114 : Oid left_hash_function;
115 : Oid right_hash_function;
116 :
117 13650 : eq_function = get_opcode(eq_opr);
118 13650 : if (!get_op_hash_functions(eq_opr,
119 : &left_hash_function, &right_hash_function))
120 0 : elog(ERROR, "could not find hash function for hash operator %u",
121 : eq_opr);
122 : /* We're not supporting cross-type cases here */
123 : Assert(left_hash_function == right_hash_function);
124 13650 : (*eqFuncOids)[i] = eq_function;
125 13650 : fmgr_info(right_hash_function, &(*hashFunctions)[i]);
126 : }
127 8300 : }
128 :
129 :
130 : /*****************************************************************************
131 : * Utility routines for all-in-memory hash tables
132 : *
133 : * These routines build hash tables for grouping tuples together (eg, for
134 : * hash aggregation). There is one entry for each not-distinct set of tuples
135 : * presented.
136 : *****************************************************************************/
137 :
138 : /*
139 : * Construct an empty TupleHashTable
140 : *
141 : * parent: PlanState node that will own this hash table
142 : * inputDesc: tuple descriptor for input tuples
143 : * inputOps: slot ops for input tuples, or NULL if unknown or not fixed
144 : * numCols: number of columns to be compared (length of next 4 arrays)
145 : * keyColIdx: indexes of tuple columns to compare
146 : * eqfuncoids: OIDs of equality comparison functions to use
147 : * hashfunctions: FmgrInfos of datatype-specific hashing functions to use
148 : * collations: collations to use in comparisons
149 : * nelements: initial estimate of hashtable size
150 : * additionalsize: size of data that may be stored along with the hash entry
151 : * metacxt: memory context for long-lived data and the simplehash table
152 : * tuplescxt: memory context in which to store the hashed tuples themselves
153 : * tempcxt: short-lived context for evaluation hash and comparison functions
154 : * use_variable_hash_iv: if true, adjust hash IV per-parallel-worker
155 : *
156 : * The hashfunctions array may be made with execTuplesHashPrepare(). Note they
157 : * are not cross-type functions, but expect to see the table datatype(s)
158 : * on both sides.
159 : *
160 : * Note that the keyColIdx, hashfunctions, and collations arrays must be
161 : * allocated in storage that will live as long as the hashtable does.
162 : *
163 : * The metacxt and tuplescxt are separate because it's usually desirable for
164 : * tuplescxt to be a BumpContext to avoid memory wastage, while metacxt must
165 : * support pfree in case the simplehash table needs to be enlarged. (We could
166 : * simplify the API of TupleHashTables by managing the tuplescxt internally.
167 : * But that would be disadvantageous to nodeAgg.c and nodeSubplan.c, which use
168 : * a single tuplescxt for multiple TupleHashTables that are reset together.)
169 : *
170 : * LookupTupleHashEntry, FindTupleHashEntry, and related functions may leak
171 : * memory in the tempcxt. It is caller's responsibility to reset that context
172 : * reasonably often, typically once per tuple. (We do it that way, rather
173 : * than managing an extra context within the hashtable, because in many cases
174 : * the caller can specify a tempcxt that it needs to reset per-tuple anyway.)
175 : *
176 : * We don't currently provide DestroyTupleHashTable functionality; the hash
177 : * table will be cleaned up at destruction of the metacxt. (Some callers
178 : * bother to delete the tuplescxt explicitly, though it'd be sufficient to
179 : * ensure it's a child of the metacxt.) There's not much point in working
180 : * harder than this so long as the expression-evaluation infrastructure
181 : * behaves similarly.
182 : */
183 : TupleHashTable
184 7350 : BuildTupleHashTable(PlanState *parent,
185 : TupleDesc inputDesc,
186 : const TupleTableSlotOps *inputOps,
187 : int numCols,
188 : AttrNumber *keyColIdx,
189 : const Oid *eqfuncoids,
190 : FmgrInfo *hashfunctions,
191 : Oid *collations,
192 : double nelements,
193 : Size additionalsize,
194 : MemoryContext metacxt,
195 : MemoryContext tuplescxt,
196 : MemoryContext tempcxt,
197 : bool use_variable_hash_iv)
198 : {
199 : TupleHashTable hashtable;
200 : uint32 nbuckets;
201 : MemoryContext oldcontext;
202 7350 : uint32 hash_iv = 0;
203 :
204 : /*
205 : * tuplehash_create requires a uint32 element count, so we had better
206 : * clamp the given nelements to fit in that. As long as we have to do
207 : * that, we might as well protect against completely insane input like
208 : * zero or NaN. But it is not our job here to enforce issues like staying
209 : * within hash_mem: the caller should have done that, and we don't have
210 : * enough info to second-guess.
211 : */
212 7350 : if (isnan(nelements) || nelements <= 0)
213 0 : nbuckets = 1;
214 7350 : else if (nelements >= PG_UINT32_MAX)
215 0 : nbuckets = PG_UINT32_MAX;
216 : else
217 7350 : nbuckets = (uint32) nelements;
218 :
219 : /* tuplescxt must be separate, else ResetTupleHashTable breaks things */
220 : Assert(metacxt != tuplescxt);
221 :
222 : /* ensure additionalsize is maxalign'ed */
223 7350 : additionalsize = MAXALIGN(additionalsize);
224 :
225 7350 : oldcontext = MemoryContextSwitchTo(metacxt);
226 :
227 7350 : hashtable = (TupleHashTable) palloc(sizeof(TupleHashTableData));
228 :
229 7350 : hashtable->numCols = numCols;
230 7350 : hashtable->keyColIdx = keyColIdx;
231 7350 : hashtable->tab_collations = collations;
232 7350 : hashtable->tuplescxt = tuplescxt;
233 7350 : hashtable->tempcxt = tempcxt;
234 7350 : hashtable->additionalsize = additionalsize;
235 7350 : hashtable->tableslot = NULL; /* will be made on first lookup */
236 7350 : hashtable->inputslot = NULL;
237 7350 : hashtable->in_hash_expr = NULL;
238 7350 : hashtable->cur_eq_func = NULL;
239 :
240 : /*
241 : * If parallelism is in use, even if the leader backend is performing the
242 : * scan itself, we don't want to create the hashtable exactly the same way
243 : * in all workers. As hashtables are iterated over in keyspace-order,
244 : * doing so in all processes in the same way is likely to lead to
245 : * "unbalanced" hashtables when the table size initially is
246 : * underestimated.
247 : */
248 7350 : if (use_variable_hash_iv)
249 994 : hash_iv = murmurhash32(ParallelWorkerNumber);
250 :
251 7350 : hashtable->hashtab = tuplehash_create(metacxt, nbuckets, hashtable);
252 :
253 : /*
254 : * We copy the input tuple descriptor just for safety --- we assume all
255 : * input tuples will have equivalent descriptors.
256 : */
257 7350 : hashtable->tableslot = MakeSingleTupleTableSlot(CreateTupleDescCopy(inputDesc),
258 : &TTSOpsMinimalTuple);
259 :
260 : /* build hash ExprState for all columns */
261 7350 : hashtable->tab_hash_expr = ExecBuildHash32FromAttrs(inputDesc,
262 : inputOps,
263 : hashfunctions,
264 : collations,
265 : numCols,
266 : keyColIdx,
267 : parent,
268 : hash_iv);
269 :
270 : /* build comparator for all columns */
271 7350 : hashtable->tab_eq_func = ExecBuildGroupingEqual(inputDesc, inputDesc,
272 : inputOps,
273 : &TTSOpsMinimalTuple,
274 : numCols,
275 : keyColIdx, eqfuncoids, collations,
276 : parent);
277 :
278 : /*
279 : * While not pretty, it's ok to not shut down this context, but instead
280 : * rely on the containing memory context being reset, as
281 : * ExecBuildGroupingEqual() only builds a very simple expression calling
282 : * functions (i.e. nothing that'd employ RegisterExprContextCallback()).
283 : */
284 7350 : hashtable->exprcontext = CreateStandaloneExprContext();
285 :
286 7350 : MemoryContextSwitchTo(oldcontext);
287 :
288 7350 : return hashtable;
289 : }
290 :
291 : /*
292 : * Reset contents of the hashtable to be empty, preserving all the non-content
293 : * state.
294 : *
295 : * Note: in usages where several TupleHashTables share a tuplescxt, all must
296 : * be reset together, as the first one's reset call will destroy all their
297 : * data. The additional reset calls for the rest will redundantly reset the
298 : * tuplescxt. But because of mcxt.c's isReset flag, that's cheap enough that
299 : * we need not avoid it.
300 : */
301 : void
302 194740 : ResetTupleHashTable(TupleHashTable hashtable)
303 : {
304 194740 : tuplehash_reset(hashtable->hashtab);
305 194740 : MemoryContextReset(hashtable->tuplescxt);
306 194740 : }
307 :
308 : /*
309 : * Estimate the amount of space needed for a TupleHashTable with nentries
310 : * entries, if the tuples have average data width tupleWidth and the caller
311 : * requires additionalsize extra space per entry.
312 : *
313 : * Return SIZE_MAX if it'd overflow size_t.
314 : *
315 : * nentries is "double" because this is meant for use by the planner,
316 : * which typically works with double rowcount estimates. So we'd need to
317 : * clamp to integer somewhere and that might as well be here. We do expect
318 : * the value not to be NaN or negative, else the result will be garbage.
319 : */
320 : Size
321 4770 : EstimateTupleHashTableSpace(double nentries,
322 : Size tupleWidth,
323 : Size additionalsize)
324 : {
325 : Size sh_space;
326 : double tuples_space;
327 :
328 : /* First estimate the space needed for the simplehash table */
329 4770 : sh_space = tuplehash_estimate_space(nentries);
330 :
331 : /* Give up if that's already too big */
332 4770 : if (sh_space >= SIZE_MAX)
333 6 : return sh_space;
334 :
335 : /*
336 : * Compute space needed for hashed tuples with additional data. nentries
337 : * must be somewhat sane, so it should be safe to compute this product.
338 : *
339 : * We assume that the hashed tuples will be kept in a BumpContext so that
340 : * there is not additional per-tuple overhead.
341 : *
342 : * (Note that this is only accurate if MEMORY_CONTEXT_CHECKING is off,
343 : * else bump.c will add a MemoryChunk header to each tuple. However, it
344 : * seems undesirable for debug builds to make different planning choices
345 : * than production builds, so we assume the production behavior always.)
346 : */
347 4764 : tuples_space = nentries * (MAXALIGN(SizeofMinimalTupleHeader) +
348 4764 : MAXALIGN(tupleWidth) +
349 4764 : MAXALIGN(additionalsize));
350 :
351 : /*
352 : * Check for size_t overflow. This coding is trickier than it may appear,
353 : * because on 64-bit machines SIZE_MAX cannot be represented exactly as a
354 : * double. We must cast it explicitly to suppress compiler warnings about
355 : * an inexact conversion, and we must trust that any double value that
356 : * compares strictly less than "(double) SIZE_MAX" will cast to a
357 : * representable size_t value.
358 : */
359 4764 : if (sh_space + tuples_space >= (double) SIZE_MAX)
360 0 : return SIZE_MAX;
361 :
362 : /* We don't bother estimating size of the miscellaneous overhead data */
363 4764 : return (Size) (sh_space + tuples_space);
364 : }
365 :
366 : /*
367 : * Find or create a hashtable entry for the tuple group containing the
368 : * given tuple. The tuple must be the same type as the hashtable entries.
369 : *
370 : * If isnew is NULL, we do not create new entries; we return NULL if no
371 : * match is found.
372 : *
373 : * If hash is not NULL, we set it to the calculated hash value. This allows
374 : * callers access to the hash value even if no entry is returned.
375 : *
376 : * If isnew isn't NULL, then a new entry is created if no existing entry
377 : * matches. On return, *isnew is true if the entry is newly created,
378 : * false if it existed already. The additional data in the new entry has
379 : * been zeroed.
380 : */
381 : TupleHashEntry
382 8017494 : LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
383 : bool *isnew, uint32 *hash)
384 : {
385 : TupleHashEntry entry;
386 : MemoryContext oldContext;
387 : uint32 local_hash;
388 :
389 : /* Need to run the hash functions in short-lived context */
390 8017494 : oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
391 :
392 : /* set up data needed by hash and match functions */
393 8017494 : hashtable->inputslot = slot;
394 8017494 : hashtable->in_hash_expr = hashtable->tab_hash_expr;
395 8017494 : hashtable->cur_eq_func = hashtable->tab_eq_func;
396 :
397 8017494 : local_hash = TupleHashTableHash_internal(hashtable->hashtab, NULL);
398 8017488 : entry = LookupTupleHashEntry_internal(hashtable, slot, isnew, local_hash);
399 :
400 8017488 : if (hash != NULL)
401 7050354 : *hash = local_hash;
402 :
403 : Assert(entry == NULL || entry->hash == local_hash);
404 :
405 8017488 : MemoryContextSwitchTo(oldContext);
406 :
407 8017488 : return entry;
408 : }
409 :
410 : /*
411 : * Compute the hash value for a tuple
412 : */
413 : uint32
414 0 : TupleHashTableHash(TupleHashTable hashtable, TupleTableSlot *slot)
415 : {
416 : MemoryContext oldContext;
417 : uint32 hash;
418 :
419 0 : hashtable->inputslot = slot;
420 0 : hashtable->in_hash_expr = hashtable->tab_hash_expr;
421 :
422 : /* Need to run the hash functions in short-lived context */
423 0 : oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
424 :
425 0 : hash = TupleHashTableHash_internal(hashtable->hashtab, NULL);
426 :
427 0 : MemoryContextSwitchTo(oldContext);
428 :
429 0 : return hash;
430 : }
431 :
432 : /*
433 : * A variant of LookupTupleHashEntry for callers that have already computed
434 : * the hash value.
435 : */
436 : TupleHashEntry
437 1216776 : LookupTupleHashEntryHash(TupleHashTable hashtable, TupleTableSlot *slot,
438 : bool *isnew, uint32 hash)
439 : {
440 : TupleHashEntry entry;
441 : MemoryContext oldContext;
442 :
443 : /* Need to run the hash functions in short-lived context */
444 1216776 : oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
445 :
446 : /* set up data needed by hash and match functions */
447 1216776 : hashtable->inputslot = slot;
448 1216776 : hashtable->in_hash_expr = hashtable->tab_hash_expr;
449 1216776 : hashtable->cur_eq_func = hashtable->tab_eq_func;
450 :
451 1216776 : entry = LookupTupleHashEntry_internal(hashtable, slot, isnew, hash);
452 : Assert(entry == NULL || entry->hash == hash);
453 :
454 1216776 : MemoryContextSwitchTo(oldContext);
455 :
456 1216776 : return entry;
457 : }
458 :
459 : /*
460 : * Search for a hashtable entry matching the given tuple. No entry is
461 : * created if there's not a match. This is similar to the non-creating
462 : * case of LookupTupleHashEntry, except that it supports cross-type
463 : * comparisons, in which the given tuple is not of the same type as the
464 : * table entries. The caller must provide the hash ExprState to use for
465 : * the input tuple, as well as the equality ExprState, since these may be
466 : * different from the table's internal functions.
467 : */
468 : TupleHashEntry
469 1013296 : FindTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
470 : ExprState *eqcomp,
471 : ExprState *hashexpr)
472 : {
473 : TupleHashEntry entry;
474 : MemoryContext oldContext;
475 : MinimalTuple key;
476 :
477 : /* Need to run the hash functions in short-lived context */
478 1013296 : oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
479 :
480 : /* Set up data needed by hash and match functions */
481 1013296 : hashtable->inputslot = slot;
482 1013296 : hashtable->in_hash_expr = hashexpr;
483 1013296 : hashtable->cur_eq_func = eqcomp;
484 :
485 : /* Search the hash table */
486 1013296 : key = NULL; /* flag to reference inputslot */
487 1013296 : entry = tuplehash_lookup(hashtable->hashtab, key);
488 1013296 : MemoryContextSwitchTo(oldContext);
489 :
490 1013296 : return entry;
491 : }
492 :
493 : /*
494 : * If tuple is NULL, use the input slot instead. This convention avoids the
495 : * need to materialize virtual input tuples unless they actually need to get
496 : * copied into the table.
497 : *
498 : * Also, the caller must select an appropriate memory context for running
499 : * the hash functions.
500 : */
501 : static uint32
502 9030790 : TupleHashTableHash_internal(struct tuplehash_hash *tb,
503 : MinimalTuple tuple)
504 : {
505 9030790 : TupleHashTable hashtable = (TupleHashTable) tb->private_data;
506 : uint32 hashkey;
507 : TupleTableSlot *slot;
508 : bool isnull;
509 :
510 9030790 : if (tuple == NULL)
511 : {
512 : /* Process the current input tuple for the table */
513 9030790 : hashtable->exprcontext->ecxt_innertuple = hashtable->inputslot;
514 9030790 : hashkey = DatumGetUInt32(ExecEvalExpr(hashtable->in_hash_expr,
515 : hashtable->exprcontext,
516 : &isnull));
517 : }
518 : else
519 : {
520 : /*
521 : * Process a tuple already stored in the table.
522 : *
523 : * (this case never actually occurs due to the way simplehash.h is
524 : * used, as the hash-value is stored in the entries)
525 : */
526 0 : slot = hashtable->exprcontext->ecxt_innertuple = hashtable->tableslot;
527 0 : ExecStoreMinimalTuple(tuple, slot, false);
528 0 : hashkey = DatumGetUInt32(ExecEvalExpr(hashtable->tab_hash_expr,
529 : hashtable->exprcontext,
530 : &isnull));
531 : }
532 :
533 : /*
534 : * The hashing done above, even with an initial value, doesn't tend to
535 : * result in good hash perturbation. Running the value produced above
536 : * through murmurhash32 leads to near perfect hash perturbation.
537 : */
538 9030784 : return murmurhash32(hashkey);
539 : }
540 :
541 : /*
542 : * Does the work of LookupTupleHashEntry and LookupTupleHashEntryHash. Useful
543 : * so that we can avoid switching the memory context multiple times for
544 : * LookupTupleHashEntry.
545 : *
546 : * NB: This function may or may not change the memory context. Caller is
547 : * expected to change it back.
548 : */
549 : static inline TupleHashEntry
550 9234264 : LookupTupleHashEntry_internal(TupleHashTable hashtable, TupleTableSlot *slot,
551 : bool *isnew, uint32 hash)
552 : {
553 : TupleHashEntryData *entry;
554 : bool found;
555 : MinimalTuple key;
556 :
557 9234264 : key = NULL; /* flag to reference inputslot */
558 :
559 9234264 : if (isnew)
560 : {
561 7500676 : entry = tuplehash_insert_hash(hashtable->hashtab, key, hash, &found);
562 :
563 7500676 : if (found)
564 : {
565 : /* found pre-existing entry */
566 6484636 : *isnew = false;
567 : }
568 : else
569 : {
570 : /* created new entry */
571 1016040 : *isnew = true;
572 :
573 1016040 : MemoryContextSwitchTo(hashtable->tuplescxt);
574 :
575 : /*
576 : * Copy the first tuple into the tuples context, and request
577 : * additionalsize extra bytes before the allocation.
578 : *
579 : * The caller can get a pointer to the additional data with
580 : * TupleHashEntryGetAdditional(), and store arbitrary data there.
581 : * Placing both the tuple and additional data in the same
582 : * allocation avoids the need to store an extra pointer in
583 : * TupleHashEntryData or allocate an additional chunk.
584 : */
585 1016040 : entry->firstTuple = ExecCopySlotMinimalTupleExtra(slot,
586 : hashtable->additionalsize);
587 : }
588 : }
589 : else
590 : {
591 1733588 : entry = tuplehash_lookup_hash(hashtable->hashtab, key, hash);
592 : }
593 :
594 9234264 : return entry;
595 : }
596 :
597 : /*
598 : * See whether two tuples (presumably of the same hash value) match
599 : */
600 : static int
601 7047454 : TupleHashTableMatch(struct tuplehash_hash *tb, MinimalTuple tuple1, MinimalTuple tuple2)
602 : {
603 : TupleTableSlot *slot1;
604 : TupleTableSlot *slot2;
605 7047454 : TupleHashTable hashtable = (TupleHashTable) tb->private_data;
606 7047454 : ExprContext *econtext = hashtable->exprcontext;
607 :
608 : /*
609 : * We assume that simplehash.h will only ever call us with the first
610 : * argument being an actual table entry, and the second argument being
611 : * LookupTupleHashEntry's dummy TupleHashEntryData. The other direction
612 : * could be supported too, but is not currently required.
613 : */
614 : Assert(tuple1 != NULL);
615 7047454 : slot1 = hashtable->tableslot;
616 7047454 : ExecStoreMinimalTuple(tuple1, slot1, false);
617 : Assert(tuple2 == NULL);
618 7047454 : slot2 = hashtable->inputslot;
619 :
620 : /* For crosstype comparisons, the inputslot must be first */
621 7047454 : econtext->ecxt_innertuple = slot2;
622 7047454 : econtext->ecxt_outertuple = slot1;
623 7047454 : return !ExecQualAndReset(hashtable->cur_eq_func, econtext);
624 : }
|