Subject: Safer hash table initialization macro

From: Bertrand Drouvot <[email-masked]>
Date: Mon, 1 Dec 2025 13:45:00 +0000

Hi hackers,

Currently to create a hash table we do things like:

A) create a struct, say:

 typedef struct SeenRelsEntry
 {
    Oid   rel_id;
    int   list_index;
 } SeenRelsEntry;

where the first member is the hash key, and then later:

B)

 ctl.keysize = sizeof(Oid);
 ctl.entrysize = sizeof(SeenRelsEntry);
 ctl.hcxt = CurrentMemoryContext;

 seen_rels = hash_create("find_all_inheritors temporary table",
                         32, /* start small and extend */
                         &ctl,

I can see 2 possible issues:

1)

We manually specify the type for keysize, which could become incorrect (from the
start) or if the key member's type changes.

2) 

It may be possible to remove the key member without the compiler noticing it.

Take this example and remove:

diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 929bb53b620..eb11976afef 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -36,7 +36,6 @@
  */
 typedef struct SeenRelsEntry
 {
-       Oid                     rel_id;                 /* relation oid */
        int                     list_index;             /* its position in output list(s) */
 } SeenRelsEntry;

That would compile without any issues because this rel_id member is not
referenced in the code (for this particular example). That's rare but possible.

But then, on my machine, during make check:

TRAP: failed Assert("!found"), File: "nodeModifyTable.c", Line: 5157, PID: 140430

The reason is that the struct member access is done only for bytes level
operations (within the hash related macros). So it's easy to think that this
member is unused (because it is not referenced in the code).

I'm thinking about what kind of safety we could put in place to better deal with
1) and 2).

What about adding a macro that:

- requests the key member name
- ensures that it is at offset 0
- computes the key size based on the member

Something like:

"
#define HASH_ELEM_INIT(ctl, entrytype, keymember) \
    do { \
        StaticAssertStmt(offsetof(entrytype, keymember) == 0, \
                        #keymember " must be first member in " #entrytype); \
        (ctl).keysize = sizeof(((entrytype *)0)->keymember); \
        (ctl).entrysize = sizeof(entrytype); \
    } while (0)
"

That way:

- The key member is explicitly referenced in the code (preventing "unused"
false positives)
- The key size is automatically computed from the actual member type (preventing
type mismatches)
- We enforce that the key is at offset 0

An additional benefit: it avoids repeating the "keysize ="  followed by "entrysize ="
in a lot of places in the code (currently about 100 times).

If that sounds like a good idea, I could work on a patch doing so.

Thoughts?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 1 Dec 2025 15:44:41 +0100

> Thoughts?

I think the hashtable creation API in postgres is so terrible that it
actively discourages usage. At Citus we definitely had the problem
that we would use Lists for cases where a hashtable was preferable
perf wise, just because the API was so terrible.
That's why I eventually implemented some wrapper helper functions to
make it less verbose and error prone to use in by far the most common
patterns we had[1].

So I'm definitely in favor of improving this API (probably by adding a
few new functions). I have a few main thoughts on what could be
improved:

1. Automatically determine keysize and entrysize given a keymember and
entrytype (like you suggested).
2. Autodect most of the flags.
    a. HASH_PARTITION, HASH_SEGMENT, HASH_FUNCTION, HASH_DIRSIZE,
HASH_COMPARE, HASH_KEYCOPY, HASH_ALLOC can all be simply be detected
based on the relevant fields from HASHCTL. Passing them in explicitly
is just duplication that causes code noise and is easy to forget
accidentally.
    b. HASH_ELEM is useless noise because it is required
    c. HASH_BLOBS could be the default (and maybe use HASH_STRINGS by
default if the keytype is char*)
3. Default to CurrentMemoryContext instead of TopMemoryContext. Like
all of the other functions that allocate, because right now it's way
too easy to accidentally use TopMemoryContext when you did not intend
to.
4. Have a creation function that doesn't require HASHCTL at all (just
takes entrytype and keymember and maybe a version of this that takes a
memorycontext).

[1]: https://github.com/citusdata/citus/blob/ae2eb65be082d52db646b68a644474e24bc6cea1/src/include/distributed/hash_helpers.h#L74

---

From: Bertrand Drouvot <[email-masked]>
Date: Wed, 3 Dec 2025 11:17:43 +0000

Hi,

On Mon, Dec 01, 2025 at 03:44:41PM +0100, Jelte Fennema-Nio wrote:
> actively discourages usage.

Thanks for sharing your thoughts!

> entrytype (like you suggested).

PFA a patch introducing and using the new macro. Note that it also introduces
HASH_ELEM_INIT_FULL for the rare cases where the whole struct is the
key.

Also one case remains untouched:

$ git grep "entrysize = sizeof" "*.c"
src/backend/replication/logical/relation.c:     ctl.entrysize = sizeof(LogicalRepRelMapEntry);

That's because the key is a member of a nested struct so that the new
macro can not be used. As there is only one occurrence of it, I think we can keep
it as it is. But we could create a dedicated macro for those cases if we feel
the need. Now that I'm writing this, that might be a better idea: that way we'd
avoid any "entrysize/keysize = " in the .c files.

Also a nice side effect of using the macros:

138 insertions(+), 203 deletions(-)

> takes entrytype and keymember and maybe a version of this that takes a

Thanks for the above suggestions! I did not think so deep as you did during
your Citus time, but will think about those too. I suggest we move forward one
step at a time, first step being the new macros. Does that make sense to you?

Regards,

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Thu, 04 Dec 2025 16:29:55 +0100

On Wed Dec 3, 2025 at 12:17 PM CET, Bertrand Drouvot wrote:
> I suggest we move forward one

Normally I would agree, but in this case I think the new macros you
proposing would become obsolete once we have the better hash table
creation functions I have in mind. And if we're going to update all
places where we create hash tables, I'd rather update them to something
really nice than a small improvement.

I couldn't let it go (nerd-sniped). So here's a patchset that adds some
macros that I think are pretty nice. Including a foreach_hash macro.

I'm a bit on the fence about the C11 _Generic code to determine whether
we should use HASH_BLOBS or HASH_STRINGS based on the type of the key.
It works really nicely in practice, but I'm worried it's a bit too much
magic. Probably we should at least have an override to allow using
HASH_BLOBS anyway for a char array (in case it's not null terminated).
From 23e79b2ff4c05d4248296b72d609ae85bf630789 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v2 1/5] Add hash_make macros

This adds a bunch of hash_make* and shmem_hash_make* macros to make it
easier and less error prone to create HTABs. These macros are
implemented as wrappers around the already existing hash_create
function. Using the new macros is preferred, due to the additional
compile time checks that they bring.

Co-Authored-By: Bertrand Drouvot <[email-masked]>
---
 src/backend/utils/hash/dynahash.c | 103 +++++++++++++++++++
 src/include/storage/shmem.h       |  32 ++++++
 src/include/utils/hsearch.h       | 164 +++++++++++++++++++++++++++++-
 src/tools/pgindent/typedefs.list  |   1 +
 4 files changed, 295 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index ac94b9e93c6..024d4d19bf6 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -631,6 +631,109 @@ hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
 	return hashp;
 }
 
+/*
+ * hash_opts_init -- initialize HASHCTL and flags from HASHOPTS
+ *
+ * This processes HASHOPTS fields into HASHCTL and flags. It's used by code
+ * that needs to call the low-level hash_create function.
+ */
+void
+hash_opts_init(HASHCTL *ctl, int *flags,
+			   Size keysize, Size entrysize, bool string_key,
+			   const HASHOPTS *opts)
+{
+	MemSet(ctl, 0, sizeof(*ctl));
+	ctl->keysize = keysize;
+	ctl->entrysize = entrysize;
+
+	*flags = HASH_ELEM;
+
+	if (opts != NULL && opts->hash != NULL)
+	{
+		ctl->hash = opts->hash;
+		*flags |= HASH_FUNCTION;
+	}
+	else
+	{
+		*flags |= string_key ? HASH_STRINGS : HASH_BLOBS;
+	}
+
+	if (opts != NULL && opts->match != NULL)
+	{
+		ctl->match = opts->match;
+		*flags |= HASH_COMPARE;
+	}
+
+	if (opts != NULL && opts->keycopy != NULL)
+	{
+		ctl->keycopy = opts->keycopy;
+		*flags |= HASH_KEYCOPY;
+	}
+
+	if (opts != NULL && opts->alloc != NULL)
+	{
+		ctl->alloc = opts->alloc;
+		*flags |= HASH_ALLOC;
+	}
+
+	if (opts != NULL && opts->num_partitions > 0)
+	{
+		ctl->num_partitions = opts->num_partitions;
+		*flags |= HASH_PARTITION;
+	}
+
+	if (opts != NULL && opts->fixed_size)
+		*flags |= HASH_FIXED_SIZE;
+}
+
+/*
+ * hash_make_impl -- simplified hash table creation
+ *
+ * This is the implementation function for the hash_make() and hash_make_ext()
+ * macros. It creates a hash table with sensible defaults.
+ *
+ * If string_key is true, the key is treated as a null-terminated string
+ * (uses HASH_STRINGS). Otherwise, the key is treated as a binary blob
+ * (uses HASH_BLOBS).
+ *
+ * Pass NULL for opts to use all defaults.
+ */
+HTAB *
+hash_make_impl(const char *tabname, int64 nelem,
+			   Size keysize, Size entrysize,
+			   bool string_key,
+			   const HASHOPTS *opts,
+			   MemoryContext mcxt)
+{
+	HASHCTL		ctl;
+	int			flags;
+
+	hash_opts_init(&ctl, &flags, keysize, entrysize, string_key, opts);
+
+	ctl.hcxt = mcxt;
+	flags |= HASH_CONTEXT;
+
+	return hash_create(tabname, nelem, &ctl, flags);
+}
+
+/*
+ * hash_make_fn_impl -- create a hash table with custom functions
+ */
+HTAB *
+hash_make_fn_impl(const char *tabname, int64 nelem,
+				  Size keysize, Size entrysize, bool string_key,
+				  HashValueFunc hashfn, HashCompareFunc matchfn,
+				  MemoryContext mcxt)
+{
+	HASHOPTS	opts = {
+		.hash = hashfn,
+		.match = matchfn
+	};
+
+	return hash_make_impl(tabname, nelem, keysize, entrysize,
+						  string_key, &opts, mcxt);
+}
+
 /*
  * Set default HASHHDR parameters.
  */
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 70a5b8b172c..74f61aebc1a 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -43,6 +43,38 @@ extern Size mul_size(Size s1, Size s2);
 
 extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
 
+/*
+ * Simplified shared memory hash table creation API
+ *
+ * These macros provide a simpler way to create shared memory hash tables by:
+ * - Automatically determining keysize and entrysize from type information
+ * - Automatically choosing HASH_STRINGS vs HASH_BLOBS based on key type
+ * - Eliminating the need for explicit HASHCTL and flags in common cases
+ *
+ * Usage:
+ *   HTAB *hash = shmem_hash_make(MyEntry, keyfield, "My hash", 64, 128);
+ *
+ * For more options (partitioning, fixed size, custom hash):
+ *   HASHOPTS opts = {.num_partitions = 16, .fixed_size = true};
+ *   HTAB *hash = shmem_hash_make_ext(MyEntry, keyfield, "My hash", 64, 128, &opts);
+ */
+#define shmem_hash_make(entrytype, keymember, tabname, init_size, max_size) \
+	shmem_hash_make_ext(entrytype, keymember, tabname, init_size, max_size, NULL)
+
+#define shmem_hash_make_ext(entrytype, keymember, tabname, init_size, max_size, opts) \
+	(StaticAssertExpr(offsetof(entrytype, keymember) == 0, \
+						 #keymember " must be first member in " #entrytype), \
+	shmem_hash_make_impl( \
+		(tabname), (init_size), (max_size), \
+		sizeof(((entrytype *)0)->keymember), \
+		sizeof(entrytype), \
+		HASH_KEY_AS_STRING(entrytype, keymember), \
+		(opts)))
+
+extern HTAB *shmem_hash_make_impl(const char *name, int64 init_size, int64 max_size,
+								  Size keysize, Size entrysize, bool string_key,
+								  const HASHOPTS *opts);
+
 /* ipci.c */
 extern void RequestAddinShmemSpace(Size size);
 
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index cb09a4cbe8c..36b31be7ad6 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -15,6 +15,9 @@
 #define HSEARCH_H

+/* Hash table control struct is an opaque type known only within dynahash.c */
+typedef struct HTAB HTAB;
+
 /*
  * Hash functions must have this signature.
  */
@@ -43,6 +46,154 @@ typedef void *(*HashCopyFunc) (void *dest, const void *src, Size keysize);
  */
 typedef void *(*HashAllocFunc) (Size request);
 
+/*
+ * Hash options for hash_make_ext and shmem_hash_make_ext macros.
+ * Contains less commonly needed options that aren't covered by the basic macros.
+ * All fields default to NULL/0/false when zero-initialized.
+ */
+typedef struct HASHOPTS
+{
+	HashValueFunc hash;			/* custom hash function (NULL for default) */
+	HashCompareFunc match;		/* custom comparison function (NULL for
+								 * default) */
+	HashCopyFunc keycopy;		/* custom key copy function (NULL for default) */
+	HashAllocFunc alloc;		/* custom allocator (NULL for default) */
+	int64		num_partitions; /* partition count (0 for none) */
+	bool		fixed_size;		/* if true, hash table cannot grow */
+} HASHOPTS;
+
+/*
+ * Helpers to detect if a type should be hashed as a string.
+ * Uses _Generic to select between string types and other types.
+ *
+ * String types include: char arrays and NameData.
+ * Everything else is treated as a binary blob (HASH_BLOBS).
+ *
+ * Note: This could be simplified once we use C23, which supports typeof.
+ */
+#define HASH_PTR_AS_STRING(ptr, size) \
+	_Generic((ptr), \
+		char(*)[size]: 1, \
+		NameData*: 1, \
+		default: 0)
+#define HASH_KEY_AS_STRING(entrytype, keymember) \
+	HASH_PTR_AS_STRING(&((entrytype *)0)->keymember, \
+					   sizeof(((entrytype *)0)->keymember))
+#define HASH_TYPE_AS_STRING(type) \
+	HASH_PTR_AS_STRING((type *)0, sizeof(type))
+
+/*
+ * Create a hash table with minimal boilerplate.
+ *
+ * This is the simplest way to create a hash table. It:
+ * - Derives keysize from the keymember's actual type
+ * - Derives entrysize from the entrytype
+ * - Automatically chooses HASH_STRINGS or HASH_BLOBS based on key type (char arrays and NameData are treated as strings)
+ * - Uses CurrentMemoryContext (not TopMemoryContext)
+ * - Validates that keymember is at offset 0
+ *
+ * Usage:
+ *   typedef struct { Oid oid; char *data; } MyEntry;
+ *   HTAB *h = hash_make(MyEntry, oid, "my table", 64);
+ */
+#define hash_make(entrytype, keymember, tabname, nelem) \
+	hash_make_cxt(entrytype, keymember, tabname, nelem, CurrentMemoryContext)
+
+/*
+ * Like hash_make, but allows specifying a memory context.
+ */
+#define hash_make_cxt(entrytype, keymember, tabname, nelem, mcxt) \
+	hash_make_ext_cxt(entrytype, keymember, tabname, nelem, NULL, mcxt)
+
+/*
+ * Hash table with custom hash and/or match functions.
+ *
+ * Like hash_make, but accepts custom hash and match function pointers.
+ * Pass NULL for hashfn to use the default hash (based on key type),
+ * or NULL for matchfn to use the default memcmp-based comparison.
+ */
+#define hash_make_fn(entrytype, keymember, tabname, nelem, hashfn, matchfn) \
+	hash_make_fn_cxt(entrytype, keymember, tabname, nelem, hashfn, matchfn, \
+					 CurrentMemoryContext)
+#define hash_make_fn_cxt(entrytype, keymember, tabname, nelem, hashfn, matchfn, mcxt) \
+	(StaticAssertExpr(offsetof(entrytype, keymember) == 0, \
+					  #keymember " must be first member in " #entrytype), \
+	 hash_make_fn_impl((tabname), (nelem), \
+					   sizeof(((entrytype *)0)->keymember), \
+					   sizeof(entrytype), \
+					   HASH_KEY_AS_STRING(entrytype, keymember), \
+					   (hashfn), (matchfn), (mcxt)))
+
+/*
+ * Hash table with extended options via HASHOPTS struct.
+ *
+ * Like hash_make, but accepts additional options via HASHOPTS struct pointer.
+ * Pass NULL for opts to use all defaults.
+ *
+ * Example usage:
+ *   HASHOPTS opts = {0};
+ *   opts.hash = my_hash_func;
+ *   opts.num_partitions = 16;
+ *   HTAB *h = hash_make_ext(MyEntry, key, "my table", 64, &opts);
+ */
+#define hash_make_ext(entrytype, keymember, tabname, nelem, opts) \
+	hash_make_ext_cxt(entrytype, keymember, tabname, nelem, opts, CurrentMemoryContext)
+
+#define hash_make_ext_cxt(entrytype, keymember, tabname, nelem, opts, mcxt) \
+	(StaticAssertExpr(offsetof(entrytype, keymember) == 0, \
+						 #keymember " must be first member in " #entrytype), \
+	hash_make_impl( \
+		(tabname), (nelem), \
+		sizeof(((entrytype *)0)->keymember), \
+		sizeof(entrytype), \
+		HASH_KEY_AS_STRING(entrytype, keymember), \
+		(opts), \
+		(mcxt)))
+
+
+/*
+ * Create a hash set where the entire entry is the key. This is
+ * like hash_make, but where the key is also the entry.
+ */
+#define hashset_make(entrytype, tabname, nelem) \
+	hashset_make_cxt(entrytype, tabname, nelem, CurrentMemoryContext)
+#define hashset_make_cxt(entrytype, tabname, nelem, mcxt) \
+	hashset_make_ext_cxt(entrytype, tabname, nelem, NULL, mcxt)
+#define hashset_make_fn(entrytype, tabname, nelem, hashfn, matchfn) \
+	hashset_make_fn_cxt(entrytype, tabname, nelem, hashfn, matchfn, \
+							CurrentMemoryContext)
+#define hashset_make_fn_cxt(entrytype, tabname, nelem, hashfn, matchfn, mcxt) \
+	hash_make_fn_impl((tabname), (nelem), sizeof(entrytype), sizeof(entrytype), \
+						 HASH_TYPE_AS_STRING(entrytype), (hashfn), (matchfn), \
+						 mcxt)
+#define hashset_make_ext(entrytype, tabname, nelem, opts) \
+	hashset_make_ext_cxt(entrytype, tabname, nelem, opts, \
+							CurrentMemoryContext)
+#define hashset_make_ext_cxt(entrytype, tabname, nelem, opts, mcxt) \
+	hash_make_impl((tabname), (nelem), sizeof(entrytype), sizeof(entrytype), \
+				   HASH_TYPE_AS_STRING(entrytype), opts, (mcxt))
+
+/*
+ * Implementation function for hash_make macros. Not meant to be called
+ * directly.
+ *
+ * If string_key is true, the key is treated as a null-terminated string.
+ * Pass NULL for opts to use all defaults.
+ */
+extern HTAB *hash_make_impl(const char *tabname, int64 nelem,
+							Size keysize, Size entrysize,
+							bool string_key,
+							const HASHOPTS *opts,
+							MemoryContext mcxt);
+
+/*
+ * Implementation function for hash_make_fn macros.
+ */
+extern HTAB *hash_make_fn_impl(const char *tabname, int64 nelem,
+							   Size keysize, Size entrysize, bool string_key,
+							   HashValueFunc hashfn, HashCompareFunc matchfn,
+							   MemoryContext mcxt);
+
 /*
  * HASHELEMENT is the private part of a hashtable entry.  The caller's data
  * follows the HASHELEMENT structure (on a MAXALIGN'd boundary).  The hash key
@@ -57,11 +208,11 @@ typedef struct HASHELEMENT
 /* Hash table header struct is an opaque type known only within dynahash.c */
 typedef struct HASHHDR HASHHDR;
 
-/* Hash table control struct is an opaque type known only within dynahash.c */
-typedef struct HTAB HTAB;
-
-/* Parameter data structure for hash_create */
-/* Only those fields indicated by hash_flags need be set */
+/*
+ * Parameter data structure for hash_create (which is the low-level method of
+ * initializing hash tables, hash_make macros are preferred)
+ * Only those fields indicated by hash_flags need be set
+ */
 typedef struct HASHCTL
 {
 	/* Used if HASH_PARTITION flag is set: */
@@ -131,6 +282,9 @@ typedef struct
  */
 extern HTAB *hash_create(const char *tabname, int64 nelem,
 						 const HASHCTL *info, int flags);
+extern void hash_opts_init(HASHCTL *ctl, int *flags,
+						   Size keysize, Size entrysize, bool string_key,
+						   const HASHOPTS *opts);
 extern void hash_destroy(HTAB *hashp);
 extern void hash_stats(const char *caller, HTAB *hashp);
 extern void *hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cf3f6a7dafd..b7cf900f1c6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1144,6 +1144,7 @@ HASHBUCKET
 HASHCTL
 HASHELEMENT
 HASHHDR
+HASHOPTS
 HASHSEGMENT
 HASH_SEQ_STATUS
 HE

base-commit: 1b2bb5077e9e9deac60a3e92e742465e7bcd3a21
-- 
2.52.0

From bf49046d1945eb0c05861c67251f4396235ac186 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:36:19 +0100
Subject: [PATCH v2 2/5] Use hash_make macros throughout the codebase

This shows how our code base looks when using the new APIs. This has
some typesafety, readability and maintanability benefits, but it also
introduces some backpatching problems. These backpatching problems
cannot be resolved by backporting the new hash_make macros, because some
of them require C11 (which we only require on master for now). I think
it's unlikely that we'll need to backpatch things in code that creates
hashtables though, so it could still be worth it to do this complete
refactor.

At the very least we should choose a few places where we use the new
macros to make sure they have coverage.
---
 contrib/dblink/dblink.c                       | 10 +--
 .../pg_stat_statements/pg_stat_statements.c   | 10 +--
 contrib/pg_trgm/trgm_regexp.c                 |  9 +--
 contrib/postgres_fdw/connection.c             | 10 +--
 contrib/postgres_fdw/shippable.c              | 10 ++-
 contrib/tablefunc/tablefunc.c                 | 17 ++---
 src/backend/access/common/heaptuple.c         | 13 +---
 src/backend/access/gist/gistbuild.c           | 11 +---
 src/backend/access/gist/gistbuildbuffers.c    | 10 +--
 src/backend/access/hash/hashpage.c            | 13 +---
 src/backend/access/heap/rewriteheap.c         | 39 ++++-------
 src/backend/access/transam/xlogprefetcher.c   |  8 +--
 src/backend/access/transam/xlogutils.c        | 13 ++--
 src/backend/catalog/pg_enum.c                 | 24 ++-----
 src/backend/catalog/pg_inherits.c             | 11 +---
 src/backend/catalog/storage.c                 | 21 ++----
 src/backend/commands/async.c                  | 14 ++--
 src/backend/commands/prepare.c                | 12 +---
 src/backend/commands/sequence.c               | 10 +--
 src/backend/commands/tablecmds.c              | 16 +----
 src/backend/executor/nodeModifyTable.c        | 10 +--
 src/backend/nodes/extensible.c                | 10 +--
 src/backend/optimizer/util/plancat.c          | 17 ++---
 src/backend/optimizer/util/predtest.c         |  9 +--
 src/backend/optimizer/util/relnode.c          | 13 +---
 src/backend/parser/parse_oper.c               | 10 ++-
 src/backend/partitioning/partdesc.c           |  9 +--
 src/backend/postmaster/autovacuum.c           | 22 ++-----
 src/backend/postmaster/checkpointer.c         | 12 +---
 .../replication/logical/applyparallelworker.c | 13 +---
 src/backend/replication/logical/relation.c    | 22 ++-----
 .../replication/logical/reorderbuffer.c       | 29 ++------
 src/backend/replication/logical/tablesync.c   | 10 ++-
 src/backend/replication/pgoutput/pgoutput.c   | 11 +---
 src/backend/storage/buffer/buf_table.c        | 15 ++---
 src/backend/storage/buffer/bufmgr.c           | 11 ++--
 src/backend/storage/buffer/localbuf.c         | 11 +---
 src/backend/storage/file/reinit.c             |  8 +--
 src/backend/storage/ipc/shmem.c               | 33 +++++++---
 src/backend/storage/ipc/standby.c             | 20 ++----
 src/backend/storage/lmgr/lock.c               | 55 +++++-----------
 src/backend/storage/lmgr/lwlock.c             |  8 +--
 src/backend/storage/lmgr/predicate.c          | 66 ++++++++-----------
 src/backend/storage/smgr/smgr.c               | 10 ++-
 src/backend/storage/sync/sync.c               | 11 +---
 src/backend/tsearch/ts_typanalyze.c           | 13 +---
 src/backend/utils/activity/wait_event.c       | 23 +++----
 src/backend/utils/adt/array_typanalyze.c      | 23 ++-----
 src/backend/utils/adt/json.c                  | 15 +----
 src/backend/utils/adt/jsonfuncs.c             | 20 ++----
 src/backend/utils/adt/mcxtfuncs.c             | 11 +---
 src/backend/utils/adt/ri_triggers.c           | 29 ++++----
 src/backend/utils/adt/ruleutils.c             | 23 ++-----
 src/backend/utils/cache/attoptcache.c         | 16 ++---
 src/backend/utils/cache/evtcache.c            |  9 +--
 src/backend/utils/cache/funccache.c           | 14 ++--
 src/backend/utils/cache/relcache.c            | 19 ++----
 src/backend/utils/cache/relfilenumbermap.c    |  9 +--
 src/backend/utils/cache/spccache.c            |  9 +--
 src/backend/utils/cache/ts_cache.c            | 27 +++-----
 src/backend/utils/cache/typcache.c            | 33 ++++------
 src/backend/utils/fmgr/dfmgr.c                | 12 ++--
 src/backend/utils/fmgr/fmgr.c                 | 11 +---
 src/backend/utils/misc/guc.c                  | 14 ++--
 src/backend/utils/misc/injection_point.c      | 14 ++--
 src/backend/utils/mmgr/portalmem.c            | 10 +--
 src/backend/utils/time/combocid.c             | 13 +---
 src/pl/plperl/plperl.c                        | 32 +++------
 src/pl/plpgsql/src/pl_exec.c                  | 32 ++++-----
 src/pl/plpython/plpy_plpymodule.c             |  9 ++-
 src/pl/plpython/plpy_procedure.c              |  9 +--
 src/pl/tcl/pltcl.c                            | 19 ++----
 src/timezone/pgtz.c                           | 12 +---
 73 files changed, 371 insertions(+), 835 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 8bf8fc8ea2f..2951c39d69f 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2540,13 +2540,9 @@ getConnectionByName(const char *name)
 static HTAB *
 createConnHash(void)
 {
-	HASHCTL		ctl;
-
-	ctl.keysize = NAMEDATALEN;
-	ctl.entrysize = sizeof(remoteConnHashEnt);
-
-	return hash_create("Remote Con hash", NUMCONN, &ctl,
-					   HASH_ELEM | HASH_STRINGS);
+	return hash_make_cxt(remoteConnHashEnt, name,
+						 "Remote Con hash", NUMCONN,
+						 TopMemoryContext);
 }
 
 static remoteConn *
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 39208f80b5b..2c55b78e9ba 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -517,7 +517,6 @@ static void
 pgss_shmem_startup(void)
 {
 	bool		found;
-	HASHCTL		info;
 	FILE	   *file = NULL;
 	FILE	   *qfile = NULL;
 	uint32		header;
@@ -557,12 +556,9 @@ pgss_shmem_startup(void)
 		pgss->stats.stats_reset = GetCurrentTimestamp();
 	}
 
-	info.keysize = sizeof(pgssHashKey);
-	info.entrysize = sizeof(pgssEntry);
-	pgss_hash = ShmemInitHash("pg_stat_statements hash",
-							  pgss_max, pgss_max,
-							  &info,
-							  HASH_ELEM | HASH_BLOBS);
+	pgss_hash = shmem_hash_make(pgssEntry, key,
+								"pg_stat_statements hash",
+								pgss_max, pgss_max);
 
 	LWLockRelease(AddinShmemInitLock);
 
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index 149f9eb259c..ced4d333b9a 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -894,7 +894,6 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 static void
 transformGraph(TrgmNFA *trgmNFA)
 {
-	HASHCTL		hashCtl;
 	TrgmStateKey initkey;
 	TrgmState  *initstate;
 	ListCell   *lc;
@@ -906,13 +905,7 @@ transformGraph(TrgmNFA *trgmNFA)
 	trgmNFA->overflowed = false;
 
 	/* Create hashtable for states */
-	hashCtl.keysize = sizeof(TrgmStateKey);
-	hashCtl.entrysize = sizeof(TrgmState);
-	hashCtl.hcxt = CurrentMemoryContext;
-	trgmNFA->states = hash_create("Trigram NFA",
-								  1024,
-								  &hashCtl,
-								  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	trgmNFA->states = hash_make(TrgmState, stateKey, "Trigram NFA", 1024);
 	trgmNFA->nstates = 0;
 
 	/* Create initial state: ambiguous prefix, NFA's initial state */
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 953c2e0ab82..042a7fb3ac1 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -214,17 +214,13 @@ GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state)
 	/* First time through, initialize connection cache hashtable */
 	if (ConnectionHash == NULL)
 	{
-		HASHCTL		ctl;
-
 		if (pgfdw_we_get_result == 0)
 			pgfdw_we_get_result =
 				WaitEventExtensionNew("PostgresFdwGetResult");
 
-		ctl.keysize = sizeof(ConnCacheKey);
-		ctl.entrysize = sizeof(ConnCacheEntry);
-		ConnectionHash = hash_create("postgres_fdw connections", 8,
-									 &ctl,
-									 HASH_ELEM | HASH_BLOBS);
+		ConnectionHash = hash_make_cxt(ConnCacheEntry, key,
+									   "postgres_fdw connections", 8,
+									   TopMemoryContext);
 
 		/*
 		 * Register some callback functions that manage connection cleanup.
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
index da3b13b207d..66d2d0b9ff4 100644
--- a/contrib/postgres_fdw/shippable.c
+++ b/contrib/postgres_fdw/shippable.c
@@ -28,6 +28,7 @@
 #include "postgres_fdw.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 
 /* Hash table for caching the results of shippability lookups */
@@ -90,13 +91,10 @@ InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 static void
 InitializeShippableCache(void)
 {
-	HASHCTL		ctl;
-
 	/* Create the hash table. */
-	ctl.keysize = sizeof(ShippableCacheKey);
-	ctl.entrysize = sizeof(ShippableCacheEntry);
-	ShippableCacheHash =
-		hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
+	ShippableCacheHash = hash_make_cxt(ShippableCacheEntry, key,
+									   "Shippability cache", 256,
+									   TopMemoryContext);
 
 	/* Set up invalidation callback on pg_foreign_server. */
 	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 74afdc0977f..ec058688ab9 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -705,24 +705,17 @@ static HTAB *
 load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
 {
 	HTAB	   *crosstab_hash;
-	HASHCTL		ctl;
 	int			ret;
 	uint64		proc;
 	MemoryContext SPIcontext;
 
-	/* initialize the category hash table */
-	ctl.keysize = MAX_CATNAME_LEN;
-	ctl.entrysize = sizeof(crosstab_HashEnt);
-	ctl.hcxt = per_query_ctx;
-
 	/*
-	 * use INIT_CATS, defined above as a guess of how many hash table entries
-	 * to create, initially
+	 * Initialize the category hash table. Use INIT_CATS, defined above as a
+	 * guess of how many hash table entries to create, initially.
 	 */
-	crosstab_hash = hash_create("crosstab hash",
-								INIT_CATS,
-								&ctl,
-								HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+	crosstab_hash = hash_make_cxt(crosstab_HashEnt, internal_catname,
+								  "crosstab hash", INIT_CATS,
+								  per_query_ctx);
 
 	/* Connect to SPI manager */
 	SPI_connect();
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 74a52d87067..1828089ecc9 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -125,18 +125,9 @@ missing_match(const void *key1, const void *key2, Size keysize)
 static void
 init_missing_cache()
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = sizeof(missing_cache_key);
-	hash_ctl.entrysize = sizeof(missing_cache_key);
-	hash_ctl.hcxt = TopMemoryContext;
-	hash_ctl.hash = missing_hash;
-	hash_ctl.match = missing_match;
 	missing_cache =
-		hash_create("Missing Values Cache",
-					32,
-					&hash_ctl,
-					HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
+		hashset_make_fn_cxt(missing_cache_key, "Missing Values Cache", 32,
+							missing_hash, missing_match, TopMemoryContext);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index be0fd5b753d..629558d905b 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -1515,15 +1515,8 @@ typedef struct
 static void
 gistInitParentMap(GISTBuildState *buildstate)
 {
-	HASHCTL		hashCtl;
-
-	hashCtl.keysize = sizeof(BlockNumber);
-	hashCtl.entrysize = sizeof(ParentMapEntry);
-	hashCtl.hcxt = CurrentMemoryContext;
-	buildstate->parentMap = hash_create("gistbuild parent map",
-										1024,
-										&hashCtl,
-										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	buildstate->parentMap = hash_make(ParentMapEntry, childblkno,
+									  "gistbuild parent map", 1024);
 }
 
 static void
diff --git a/src/backend/access/gist/gistbuildbuffers.c b/src/backend/access/gist/gistbuildbuffers.c
index 0707254d18e..fc36c90d333 100644
--- a/src/backend/access/gist/gistbuildbuffers.c
+++ b/src/backend/access/gist/gistbuildbuffers.c
@@ -44,7 +44,6 @@ GISTBuildBuffers *
 gistInitBuildBuffers(int pagesPerBuffer, int levelStep, int maxLevel)
 {
 	GISTBuildBuffers *gfbb;
-	HASHCTL		hashCtl;
 
 	gfbb = palloc(sizeof(GISTBuildBuffers));
 	gfbb->pagesPerBuffer = pagesPerBuffer;
@@ -72,13 +71,8 @@ gistInitBuildBuffers(int pagesPerBuffer, int levelStep, int maxLevel)
 	 * nodeBuffersTab hash is association between index blocks and it's
 	 * buffers.
 	 */
-	hashCtl.keysize = sizeof(BlockNumber);
-	hashCtl.entrysize = sizeof(GISTNodeBuffer);
-	hashCtl.hcxt = CurrentMemoryContext;
-	gfbb->nodeBuffersTab = hash_create("gistbuildbuffers",
-									   1024,
-									   &hashCtl,
-									   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	gfbb->nodeBuffersTab = hash_make(GISTNodeBuffer, nodeBlocknum,
+									 "gistbuildbuffers", 1024);
 
 	gfbb->bufferEmptyingQueue = NIL;
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index b8e5bd005e5..cf71fa9cb4f 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -1356,7 +1356,6 @@ void
 _hash_finish_split(Relation rel, Buffer metabuf, Buffer obuf, Bucket obucket,
 				   uint32 maxbucket, uint32 highmask, uint32 lowmask)
 {
-	HASHCTL		hash_ctl;
 	HTAB	   *tidhtab;
 	Buffer		bucket_nbuf = InvalidBuffer;
 	Buffer		nbuf;
@@ -1367,16 +1366,8 @@ _hash_finish_split(Relation rel, Buffer metabuf, Buffer obuf, Bucket obucket,
 	Bucket		nbucket;
 	bool		found;
 
-	/* Initialize hash tables used to track TIDs */
-	hash_ctl.keysize = sizeof(ItemPointerData);
-	hash_ctl.entrysize = sizeof(ItemPointerData);
-	hash_ctl.hcxt = CurrentMemoryContext;
-
-	tidhtab =
-		hash_create("bucket ctids",
-					256,		/* arbitrary initial size */
-					&hash_ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	/* Initialize hash tables used to track TIDs (with arbitrary initial size) */
+	tidhtab = hashset_make(ItemPointerData, "bucket ctids", 256);
 
 	bucket_nblkno = nblkno = _hash_get_newblock_from_oldbucket(rel, obucket);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 66ab48f0fe0..dc2c46742fd 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -237,7 +237,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	RewriteState state;
 	MemoryContext rw_cxt;
 	MemoryContext old_cxt;
-	HASHCTL		hash_ctl;
 
 	/*
 	 * To ease cleanup, make a separate context that will contain the
@@ -262,24 +261,19 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	state->rs_cxt = rw_cxt;
 	state->rs_bulkstate = smgr_bulk_start_rel(new_heap, MAIN_FORKNUM);
 
-	/* Initialize hash tables used to track update chains */
-	hash_ctl.keysize = sizeof(TidHashKey);
-	hash_ctl.entrysize = sizeof(UnresolvedTupData);
-	hash_ctl.hcxt = state->rs_cxt;
-
+	/*
+	 * Initialize hash tables used to track update chains (with arbitrary
+	 * initial sizes)
+	 */
 	state->rs_unresolved_tups =
-		hash_create("Rewrite / Unresolved ctids",
-					128,		/* arbitrary initial size */
-					&hash_ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-
-	hash_ctl.entrysize = sizeof(OldToNewMappingData);
+		hash_make_cxt(UnresolvedTupData, key,
+					  "Rewrite / Unresolved ctids", 128,
+					  state->rs_cxt);
 
 	state->rs_old_new_tid_map =
-		hash_create("Rewrite / Old to new tid map",
-					128,		/* arbitrary initial size */
-					&hash_ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		hash_make_cxt(OldToNewMappingData, key,
+					  "Rewrite / Old to new tid map", 128,
+					  state->rs_cxt);
 
 	MemoryContextSwitchTo(old_cxt);
 
@@ -757,7 +751,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 static void
 logical_begin_heap_rewrite(RewriteState state)
 {
-	HASHCTL		hash_ctl;
 	TransactionId logical_xmin;
 
 	/*
@@ -788,15 +781,11 @@ logical_begin_heap_rewrite(RewriteState state)
 	state->rs_begin_lsn = GetXLogInsertRecPtr();
 	state->rs_num_rewrite_mappings = 0;
 
-	hash_ctl.keysize = sizeof(TransactionId);
-	hash_ctl.entrysize = sizeof(RewriteMappingFile);
-	hash_ctl.hcxt = state->rs_cxt;
-
 	state->rs_logical_mappings =
-		hash_create("Logical rewrite mapping",
-					128,		/* arbitrary initial size */
-					&hash_ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		hash_make_cxt(RewriteMappingFile, xid,
+					  "Logical rewrite mapping",
+					  128,		/* arbitrary initial size */
+					  state->rs_cxt);
 }
 
 /*
diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index ed3aacabc98..f790eea87ff 100644
--- a/src/backend/access/transam/xlogprefetcher.c
+++ b/src/backend/access/transam/xlogprefetcher.c
@@ -362,15 +362,13 @@ XLogPrefetcher *
 XLogPrefetcherAllocate(XLogReaderState *reader)
 {
 	XLogPrefetcher *prefetcher;
-	HASHCTL		ctl;
 
 	prefetcher = palloc0(sizeof(XLogPrefetcher));
 	prefetcher->reader = reader;
 
-	ctl.keysize = sizeof(RelFileLocator);
-	ctl.entrysize = sizeof(XLogPrefetcherFilter);
-	prefetcher->filter_table = hash_create("XLogPrefetcherFilterTable", 1024,
-										   &ctl, HASH_ELEM | HASH_BLOBS);
+	prefetcher->filter_table = hash_make_cxt(XLogPrefetcherFilter, rlocator,
+											 "XLogPrefetcherFilterTable", 1024,
+											 TopMemoryContext);
 	dlist_init(&prefetcher->filter_queue);
 
 	SharedStats->wal_distance = 0;
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index ce2a3e42146..9e24759f5cc 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -27,6 +27,7 @@
 #include "storage/fd.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"

@@ -131,15 +132,9 @@ log_invalid_page(RelFileLocator locator, ForkNumber forkno, BlockNumber blkno,
 	if (invalid_page_tab == NULL)
 	{
 		/* create hash table when first needed */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(xl_invalid_page_key);
-		ctl.entrysize = sizeof(xl_invalid_page);
-
-		invalid_page_tab = hash_create("XLOG invalid-page table",
-									   100,
-									   &ctl,
-									   HASH_ELEM | HASH_BLOBS);
+		invalid_page_tab = hash_make_cxt(xl_invalid_page, key,
+										 "XLOG invalid-page table", 100,
+										 TopMemoryContext);
 	}
 
 	/* we currently assume xl_invalid_page_key contains no padding */
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index da9c2a46cfa..4787a61c7d3 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -267,15 +267,9 @@ EnumValuesDelete(Oid enumTypeOid)
 static void
 init_uncommitted_enum_types(void)
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(Oid);
-	hash_ctl.hcxt = TopTransactionContext;
-	uncommitted_enum_types = hash_create("Uncommitted enum types",
-										 32,
-										 &hash_ctl,
-										 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	uncommitted_enum_types = hashset_make_cxt(Oid,
+											  "Uncommitted enum types", 32,
+											  TopTransactionContext);
 }
 
 /*
@@ -284,15 +278,9 @@ init_uncommitted_enum_types(void)
 static void
 init_uncommitted_enum_values(void)
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(Oid);
-	hash_ctl.hcxt = TopTransactionContext;
-	uncommitted_enum_values = hash_create("Uncommitted enum values",
-										  32,
-										  &hash_ctl,
-										  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	uncommitted_enum_values = hashset_make_cxt(Oid,
+											   "Uncommitted enum values", 32,
+											   TopTransactionContext);
 }
 
 /*
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 929bb53b620..c78aa727f0b 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -256,19 +256,12 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 {
 	/* hash table for O(1) rel_oid -> rel_numparents cell lookup */
 	HTAB	   *seen_rels;
-	HASHCTL		ctl;
 	List	   *rels_list,
 			   *rel_numparents;
 	ListCell   *l;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SeenRelsEntry);
-	ctl.hcxt = CurrentMemoryContext;
-
-	seen_rels = hash_create("find_all_inheritors temporary table",
-							32, /* start small and extend */
-							&ctl,
-							HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	seen_rels = hash_make(SeenRelsEntry, rel_id,
+						  "find_all_inheritors temporary table", 32);
 
 	/*
 	 * We build a list starting with the given rel and adding all direct and
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c58e9418ac3..1d13b4a1f39 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -90,15 +90,9 @@ AddPendingSync(const RelFileLocator *rlocator)
 
 	/* create the hash if not yet */
 	if (!pendingSyncHash)
-	{
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(RelFileLocator);
-		ctl.entrysize = sizeof(PendingRelSync);
-		ctl.hcxt = TopTransactionContext;
-		pendingSyncHash = hash_create("pending sync hash", 16, &ctl,
-									  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-	}
+		pendingSyncHash = hash_make_cxt(PendingRelSync, rlocator,
+										"pending sync hash", 16,
+										TopTransactionContext);
 
 	pending = hash_search(pendingSyncHash, rlocator, HASH_ENTER, &found);
 	Assert(!found);
@@ -600,7 +594,6 @@ void
 SerializePendingSyncs(Size maxSize, char *startAddress)
 {
 	HTAB	   *tmphash;
-	HASHCTL		ctl;
 	HASH_SEQ_STATUS scan;
 	PendingRelSync *sync;
 	PendingRelDelete *delete;
@@ -611,12 +604,8 @@ SerializePendingSyncs(Size maxSize, char *startAddress)
 		goto terminate;
 
 	/* Create temporary hash to collect active relfilelocators */
-	ctl.keysize = sizeof(RelFileLocator);
-	ctl.entrysize = sizeof(RelFileLocator);
-	ctl.hcxt = CurrentMemoryContext;
-	tmphash = hash_create("tmp relfilelocators",
-						  hash_get_num_entries(pendingSyncHash), &ctl,
-						  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	tmphash = hashset_make(RelFileLocator, "tmp relfilelocators",
+						   hash_get_num_entries(pendingSyncHash));
 
 	/* collect all rlocator from pending syncs */
 	hash_seq_init(&scan, pendingSyncHash);
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index eb86402cae4..9a7c8d1fb95 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -2414,20 +2414,14 @@ AddEventToPendingNotifies(Notification *n)
 	if (list_length(pendingNotifies->events) >= MIN_HASHABLE_NOTIFIES &&
 		pendingNotifies->hashtab == NULL)
 	{
-		HASHCTL		hash_ctl;
 		ListCell   *l;
 
 		/* Create the hash table */
-		hash_ctl.keysize = sizeof(Notification *);
-		hash_ctl.entrysize = sizeof(struct NotificationHash);
-		hash_ctl.hash = notification_hash;
-		hash_ctl.match = notification_match;
-		hash_ctl.hcxt = CurTransactionContext;
 		pendingNotifies->hashtab =
-			hash_create("Pending Notifies",
-						256L,
-						&hash_ctl,
-						HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+			hash_make_fn_cxt(struct NotificationHash, event,
+							 "Pending Notifies", 256,
+							 notification_hash, notification_match,
+							 CurTransactionContext);
 
 		/* Insert all the already-existing events */
 		foreach(l, pendingNotifies->events)
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 34b6410d6a2..0002c6dc993 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -371,15 +371,9 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 static void
 InitQueryHashTable(void)
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = sizeof(PreparedStatement);
-
-	prepared_queries = hash_create("Prepared Queries",
-								   32,
-								   &hash_ctl,
-								   HASH_ELEM | HASH_STRINGS);
+	prepared_queries = hash_make_cxt(PreparedStatement, stmt_name,
+									 "Prepared Queries", 32,
+									 TopMemoryContext);
 }
 
 /*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 51567994126..5fbb3ce9030 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1113,13 +1113,9 @@ lock_and_open_sequence(SeqTable seq)
 static void
 create_seq_hashtable(void)
 {
-	HASHCTL		ctl;
-
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SeqTableData);
-
-	seqhashtab = hash_create("Sequence values", 16, &ctl,
-							 HASH_ELEM | HASH_BLOBS);
+	seqhashtab = hash_make_cxt(SeqTableData, relid,
+							   "Sequence values", 16,
+							   TopMemoryContext);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 07e5b95782e..7f0fb263772 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2154,19 +2154,9 @@ ExecuteTruncateGuts(List *explicit_rels,
 
 			/* First time through, initialize hashtable for foreign tables */
 			if (!ft_htab)
-			{
-				HASHCTL		hctl;
-
-				memset(&hctl, 0, sizeof(HASHCTL));
-				hctl.keysize = sizeof(Oid);
-				hctl.entrysize = sizeof(ForeignTruncateInfo);
-				hctl.hcxt = CurrentMemoryContext;
-
-				ft_htab = hash_create("TRUNCATE for Foreign Tables",
-									  32,	/* start small and extend */
-									  &hctl,
-									  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-			}
+				ft_htab = hash_make(ForeignTruncateInfo, serverid,
+									"TRUNCATE for Foreign Tables",
+									32);	/* start small and extend */
 
 			/* Find or create cached entry for the foreign table */
 			ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e44f1223886..537901ad5a0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -5134,15 +5134,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 #endif
 	if (nrels >= MT_NRELS_HASH)
 	{
-		HASHCTL		hash_ctl;
-
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(MTTargetRelLookup);
-		hash_ctl.hcxt = CurrentMemoryContext;
 		mtstate->mt_resultOidHash =
-			hash_create("ModifyTable target hash",
-						nrels, &hash_ctl,
-						HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+			hash_make(MTTargetRelLookup, relationOid,
+					  "ModifyTable target hash", nrels);
 		for (i = 0; i < nrels; i++)
 		{
 			Oid			hashkey;
diff --git a/src/backend/nodes/extensible.c b/src/backend/nodes/extensible.c
index 3ede1ee0f5d..1c0e2b20cd1 100644
--- a/src/backend/nodes/extensible.c
+++ b/src/backend/nodes/extensible.c
@@ -22,6 +22,7 @@
 
 #include "nodes/extensible.h"
 #include "utils/hsearch.h"
+#include "utils/memutils.h"
 
 static HTAB *extensible_node_methods = NULL;
 static HTAB *custom_scan_methods = NULL;
@@ -45,13 +46,8 @@ RegisterExtensibleNodeEntry(HTAB **p_htable, const char *htable_label,
 
 	if (*p_htable == NULL)
 	{
-		HASHCTL		ctl;
-
-		ctl.keysize = EXTNODENAME_MAX_LEN;
-		ctl.entrysize = sizeof(ExtensibleNodeEntry);
-
-		*p_htable = hash_create(htable_label, 100, &ctl,
-								HASH_ELEM | HASH_STRINGS);
+		*p_htable = hash_make_cxt(ExtensibleNodeEntry, extnodename,
+								  htable_label, 100, TopMemoryContext);
 	}
 
 	if (strlen(extnodename) >= EXTNODENAME_MAX_LEN)
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 07f92fac239..32435933a93 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -694,19 +694,10 @@ get_relation_notnullatts(PlannerInfo *root, Relation relation)
 	/* create the hash table if it hasn't been created yet */
 	if (root->glob->rel_notnullatts_hash == NULL)
 	{
-		HTAB	   *hashtab;
-		HASHCTL		hash_ctl;
-
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(NotnullHashEntry);
-		hash_ctl.hcxt = CurrentMemoryContext;
-
-		hashtab = hash_create("Relation NOT NULL attnums",
-							  64L,	/* arbitrary initial size */
-							  &hash_ctl,
-							  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-
-		root->glob->rel_notnullatts_hash = hashtab;
+		root->glob->rel_notnullatts_hash =
+			hash_make(NotnullHashEntry, relid,
+					  "Relation NOT NULL attnums",
+					  64L);		/* arbitrary initial size */
 	}
 
 	/*
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index ac28573cd0a..43291f128fd 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -2117,12 +2117,9 @@ lookup_proof_cache(Oid pred_op, Oid clause_op, bool refute_it)
 	if (OprProofCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(OprProofCacheKey);
-		ctl.entrysize = sizeof(OprProofCacheEntry);
-		OprProofCacheHash = hash_create("Btree proof lookup cache", 256,
-										&ctl, HASH_ELEM | HASH_BLOBS);
+		OprProofCacheHash = hash_make_cxt(OprProofCacheEntry, key,
+										  "Btree proof lookup cache", 256,
+										  TopMemoryContext);
 
 		/* Arrange to flush cache on pg_amop changes */
 		CacheRegisterSyscacheCallback(AMOPOPID,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 1158bc194c3..3ed61fefb0a 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -601,19 +601,12 @@ static void
 build_join_rel_hash(PlannerInfo *root)
 {
 	HTAB	   *hashtab;
-	HASHCTL		hash_ctl;
 	ListCell   *l;
 
 	/* Create the hash table */
-	hash_ctl.keysize = sizeof(Relids);
-	hash_ctl.entrysize = sizeof(JoinHashEntry);
-	hash_ctl.hash = bitmap_hash;
-	hash_ctl.match = bitmap_match;
-	hash_ctl.hcxt = CurrentMemoryContext;
-	hashtab = hash_create("JoinRelHashTable",
-						  256L,
-						  &hash_ctl,
-						  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+	hashtab = hash_make_fn(JoinHashEntry, join_relids,
+						   "JoinRelHashTable", 256,
+						   bitmap_hash, bitmap_match);
 
 	/* Insert all the already-existing joinrels */
 	foreach(l, root->join_rel_list)
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 7bd7a336fd6..f9588865cfc 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -27,6 +27,7 @@
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -1028,12 +1029,9 @@ find_oper_cache_entry(OprCacheKey *key)
 	if (OprCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(OprCacheKey);
-		ctl.entrysize = sizeof(OprCacheEntry);
-		OprCacheHash = hash_create("Operator lookup cache", 256,
-								   &ctl, HASH_ELEM | HASH_BLOBS);
+		OprCacheHash = hash_make_cxt(OprCacheEntry, key,
+									 "Operator lookup cache", 256,
+									 TopMemoryContext);
 
 		/* Arrange to flush cache on pg_operator and pg_cast changes */
 		CacheRegisterSyscacheCallback(OPERNAMENSP,
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index 328b4d450e4..b3fbd781494 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -424,17 +424,12 @@ CreatePartitionDirectory(MemoryContext mcxt, bool omit_detached)
 {
 	MemoryContext oldcontext = MemoryContextSwitchTo(mcxt);
 	PartitionDirectory pdir;
-	HASHCTL		ctl;
 
 	pdir = palloc(sizeof(PartitionDirectoryData));
 	pdir->pdir_mcxt = mcxt;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(PartitionDirectoryEntry);
-	ctl.hcxt = mcxt;
-
-	pdir->pdir_hash = hash_create("partition directory", 256, &ctl,
-								  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	pdir->pdir_hash = hash_make_cxt(PartitionDirectoryEntry, reloid,
+									"partition directory", 256, mcxt);
 	pdir->omit_detached = omit_detached;
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1c38488f2cb..f1b6b1a24a9 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -907,7 +907,6 @@ rebuild_database_list(Oid newdb)
 	MemoryContext newcxt;
 	MemoryContext oldcxt;
 	MemoryContext tmpcxt;
-	HASHCTL		hctl;
 	int			score;
 	int			nelems;
 	HTAB	   *dbhash;
@@ -937,12 +936,10 @@ rebuild_database_list(Oid newdb)
 	 * score, and finally put the array elements into the new doubly linked
 	 * list.
 	 */
-	hctl.keysize = sizeof(Oid);
-	hctl.entrysize = sizeof(avl_dbase);
-	hctl.hcxt = tmpcxt;
-	dbhash = hash_create("autovacuum db hash", 20, &hctl,	/* magic number here
-															 * FIXME */
-						 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	dbhash = hash_make_cxt(avl_dbase, adl_datid,
+						   "autovacuum db hash",
+						   20,	/* magic number here FIXME */
+						   tmpcxt);
 
 	/* start by inserting the new database */
 	score = 0;
@@ -1904,7 +1901,6 @@ do_autovacuum(void)
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
-	HASHCTL		ctl;
 	HTAB	   *table_toast_map;
 	ListCell   *volatile cell;
 	BufferAccessStrategy bstrategy;
@@ -1977,13 +1973,9 @@ do_autovacuum(void)
 	pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel));
 
 	/* create hash table for toast <-> main relid mapping */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(av_relation);
-
-	table_toast_map = hash_create("TOAST to main relid map",
-								  100,
-								  &ctl,
-								  HASH_ELEM | HASH_BLOBS);
+	table_toast_map = hash_make_cxt(av_relation, ar_toastrelid,
+									"TOAST to main relid map", 100,
+									TopMemoryContext);
 
 	/*
 	 * Scan pg_class to determine which tables to vacuum.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e84e8663e96..83bae2b75b6 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -1301,7 +1301,6 @@ CompactCheckpointerRequestQueue(void)
 	int			num_requests;
 	int			read_idx,
 				write_idx;
-	HASHCTL		ctl;
 	HTAB	   *htab;
 	bool	   *skip_slot;
 
@@ -1321,14 +1320,9 @@ CompactCheckpointerRequestQueue(void)
 	head = CheckpointerShmem->head;
 
 	/* Initialize temporary hash table */
-	ctl.keysize = sizeof(CheckpointerRequest);
-	ctl.entrysize = sizeof(struct CheckpointerSlotMapping);
-	ctl.hcxt = CurrentMemoryContext;
-
-	htab = hash_create("CompactCheckpointerRequestQueue",
-					   CheckpointerShmem->num_requests,
-					   &ctl,
-					   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	htab = hash_make(struct CheckpointerSlotMapping, request,
+					 "CompactCheckpointerRequestQueue",
+					 CheckpointerShmem->num_requests);
 
 	/*
 	 * The basic idea here is that a request can be skipped if it's followed
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index baa68c1ab6c..f32ff22a98a 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -484,16 +484,9 @@ pa_allocate_worker(TransactionId xid)
 	/* First time through, initialize parallel apply worker state hashtable. */
 	if (!ParallelApplyTxnHash)
 	{
-		HASHCTL		ctl;
-
-		MemSet(&ctl, 0, sizeof(ctl));
-		ctl.keysize = sizeof(TransactionId);
-		ctl.entrysize = sizeof(ParallelApplyWorkerEntry);
-		ctl.hcxt = ApplyContext;
-
-		ParallelApplyTxnHash = hash_create("logical replication parallel apply workers hash",
-										   16, &ctl,
-										   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		ParallelApplyTxnHash = hash_make_cxt(ParallelApplyWorkerEntry, xid,
+											 "logical replication parallel apply workers hash",
+											 16, ApplyContext);
 	}
 
 	/* Create an entry for the requested transaction. */
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 10b3d0d9b82..c4af85d74f1 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -104,8 +104,6 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
 static void
 logicalrep_relmap_init(void)
 {
-	HASHCTL		ctl;
-
 	if (!LogicalRepRelMapContext)
 		LogicalRepRelMapContext =
 			AllocSetContextCreate(CacheMemoryContext,
@@ -113,12 +111,9 @@ logicalrep_relmap_init(void)
 								  ALLOCSET_DEFAULT_SIZES);
 
 	/* Initialize the relation hash table. */
-	ctl.keysize = sizeof(LogicalRepRelId);
-	ctl.entrysize = sizeof(LogicalRepRelMapEntry);
-	ctl.hcxt = LogicalRepRelMapContext;
-
-	LogicalRepRelMap = hash_create("logicalrep relation map cache", 128, &ctl,
-								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	LogicalRepRelMap = hash_make_cxt(LogicalRepRelMapEntry, remoterel.remoteid,
+									 "logicalrep relation map cache", 128,
+									 LogicalRepRelMapContext);
 
 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(logicalrep_relmap_invalidate_cb,
@@ -610,8 +605,6 @@ logicalrep_partmap_reset_relmap(LogicalRepRelation *remoterel)
 static void
 logicalrep_partmap_init(void)
 {
-	HASHCTL		ctl;
-
 	if (!LogicalRepPartMapContext)
 		LogicalRepPartMapContext =
 			AllocSetContextCreate(CacheMemoryContext,
@@ -619,12 +612,9 @@ logicalrep_partmap_init(void)
 								  ALLOCSET_DEFAULT_SIZES);
 
 	/* Initialize the relation hash table. */
-	ctl.keysize = sizeof(Oid);	/* partition OID */
-	ctl.entrysize = sizeof(LogicalRepPartMapEntry);
-	ctl.hcxt = LogicalRepPartMapContext;
-
-	LogicalRepPartMap = hash_create("logicalrep partition map cache", 64, &ctl,
-									HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	LogicalRepPartMap = hash_make_cxt(LogicalRepPartMapEntry, partoid,
+									  "logicalrep partition map cache", 64,
+									  LogicalRepPartMapContext);
 
 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(logicalrep_partmap_invalidate_cb,
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index eb6a84554b7..e09516d3d8b 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -324,7 +324,6 @@ ReorderBuffer *
 ReorderBufferAllocate(void)
 {
 	ReorderBuffer *buffer;
-	HASHCTL		hash_ctl;
 	MemoryContext new_ctx;
 
 	Assert(MyReplicationSlot != NULL);
@@ -337,8 +336,6 @@ ReorderBufferAllocate(void)
 	buffer =
 		(ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
 
-	memset(&hash_ctl, 0, sizeof(hash_ctl));
-
 	buffer->context = new_ctx;
 
 	buffer->change_context = SlabContextCreate(new_ctx,
@@ -367,12 +364,8 @@ ReorderBufferAllocate(void)
 												  SLAB_DEFAULT_BLOCK_SIZE,
 												  SLAB_DEFAULT_BLOCK_SIZE);
 
-	hash_ctl.keysize = sizeof(TransactionId);
-	hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
-	hash_ctl.hcxt = buffer->context;
-
-	buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
-								 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	buffer->by_txn = hash_make_cxt(ReorderBufferTXNByIdEnt, xid,
+								   "ReorderBufferByXid", 1000, buffer->context);
 
 	buffer->by_txn_last_xid = InvalidTransactionId;
 	buffer->by_txn_last_txn = NULL;
@@ -1836,22 +1829,17 @@ static void
 ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
 	dlist_iter	iter;
-	HASHCTL		hash_ctl;
 
 	if (!rbtxn_has_catalog_changes(txn) || dlist_is_empty(&txn->tuplecids))
 		return;
 
-	hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
-	hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
-	hash_ctl.hcxt = rb->context;
-
 	/*
 	 * create the hash with the exact number of to-be-stored tuplecids from
 	 * the start
 	 */
 	txn->tuplecid_hash =
-		hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		hash_make_cxt(ReorderBufferTupleCidEnt, key,
+					  "ReorderBufferTupleCid", txn->ntuplecids, rb->context);
 
 	dlist_foreach(iter, &txn->tuplecids)
 	{
@@ -4977,15 +4965,10 @@ StartupReorderBuffer(void)
 static void
 ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASHCTL		hash_ctl;
-
 	Assert(txn->toast_hash == NULL);
 
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
-	hash_ctl.hcxt = rb->context;
-	txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
-								  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	txn->toast_hash = hash_make_cxt(ReorderBufferToastEnt, chunk_id,
+									"ReorderBufferToastHash", 5, rb->context);
 }
 
 /*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index fa8e3bf969a..0a4a47d6c8a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -117,6 +117,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -390,12 +391,9 @@ ProcessSyncingTablesForApply(XLogRecPtr current_lsn)
 	 */
 	if (table_states_not_ready != NIL && !last_start_times)
 	{
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(struct tablesync_start_time_mapping);
-		last_start_times = hash_create("Logical replication table sync worker start times",
-									   256, &ctl, HASH_ELEM | HASH_BLOBS);
+		last_start_times = hash_make_cxt(struct tablesync_start_time_mapping, relid,
+										 "Logical replication table sync worker start times",
+										 256, TopMemoryContext);
 	}
 
 	/*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 942e1abdb58..6dea24ff0a6 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1969,7 +1969,6 @@ pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
 static void
 init_rel_sync_cache(MemoryContext cachectx)
 {
-	HASHCTL		ctl;
 	static bool relation_callbacks_registered = false;
 
 	/* Nothing to do if hash table already exists */
@@ -1977,13 +1976,9 @@ init_rel_sync_cache(MemoryContext cachectx)
 		return;
 
 	/* Make a new hash table for the cache */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RelationSyncEntry);
-	ctl.hcxt = cachectx;
-
-	RelationSyncCache = hash_create("logical replication output relation cache",
-									128, &ctl,
-									HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);
+	RelationSyncCache = hash_make_cxt(RelationSyncEntry, relid,
+									  "logical replication output relation cache",
+									  128, cachectx);
 
 	Assert(RelationSyncCache != NULL);
 
diff --git a/src/backend/storage/buffer/buf_table.c b/src/backend/storage/buffer/buf_table.c
index 9d256559bab..bfcec855ff6 100644
--- a/src/backend/storage/buffer/buf_table.c
+++ b/src/backend/storage/buffer/buf_table.c
@@ -50,19 +50,16 @@ BufTableShmemSize(int size)
 void
 InitBufTable(int size)
 {
-	HASHCTL		info;
+	HASHOPTS	opts = {0};
 
 	/* assume no locking is needed yet */
 
 	/* BufferTag maps to Buffer */
-	info.keysize = sizeof(BufferTag);
-	info.entrysize = sizeof(BufferLookupEnt);
-	info.num_partitions = NUM_BUFFER_PARTITIONS;
-
-	SharedBufHash = ShmemInitHash("Shared Buffer Lookup Table",
-								  size, size,
-								  &info,
-								  HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE);
+	opts.num_partitions = NUM_BUFFER_PARTITIONS;
+	opts.fixed_size = true;
+	SharedBufHash = shmem_hash_make_ext(BufferLookupEnt, key,
+										"Shared Buffer Lookup Table",
+										size, size, &opts);
 }
 
 /*
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f373cead95f..6f5932843cc 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -62,6 +62,7 @@
 #include "storage/smgr.h"
 #include "storage/standby.h"
 #include "utils/memdebug.h"
+#include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/rel.h"
 #include "utils/resowner.h"
@@ -4006,8 +4007,6 @@ AtEOXact_Buffers(bool isCommit)
 void
 InitBufferManagerAccess(void)
 {
-	HASHCTL		hash_ctl;
-
 	/*
 	 * An advisory limit on the number of pins each backend should hold, based
 	 * on shared_buffers and the maximum number of connections possible.
@@ -4019,11 +4018,9 @@ InitBufferManagerAccess(void)
 
 	memset(&PrivateRefCountArray, 0, sizeof(PrivateRefCountArray));
 
-	hash_ctl.keysize = sizeof(int32);
-	hash_ctl.entrysize = sizeof(PrivateRefCountEntry);
-
-	PrivateRefCountHash = hash_create("PrivateRefCount", 100, &hash_ctl,
-									  HASH_ELEM | HASH_BLOBS);
+	PrivateRefCountHash = hash_make_cxt(PrivateRefCountEntry, buffer,
+										"PrivateRefCount", 100,
+										TopMemoryContext);
 
 	/*
 	 * AtProcExit_Buffers needs LWLock access, and thereby has to be called at
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 15aac7d1c9f..a66f2975a47 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -728,7 +728,6 @@ static void
 InitLocalBuffers(void)
 {
 	int			nbufs = num_temp_buffers;
-	HASHCTL		info;
 	int			i;
 
 	/*
@@ -779,13 +778,9 @@ InitLocalBuffers(void)
 	}
 
 	/* Create the lookup hash table */
-	info.keysize = sizeof(BufferTag);
-	info.entrysize = sizeof(LocalBufferLookupEnt);
-
-	LocalBufHash = hash_create("Local Buffer Lookup Table",
-							   nbufs,
-							   &info,
-							   HASH_ELEM | HASH_BLOBS);
+	LocalBufHash = hash_make_cxt(LocalBufferLookupEnt, key,
+								 "Local Buffer Lookup Table", nbufs,
+								 TopMemoryContext);
 
 	if (!LocalBufHash)
 		elog(ERROR, "could not initialize local buffer hash table");
diff --git a/src/backend/storage/file/reinit.c b/src/backend/storage/file/reinit.c
index 5c8275cf536..a943982715b 100644
--- a/src/backend/storage/file/reinit.c
+++ b/src/backend/storage/file/reinit.c
@@ -175,7 +175,6 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op)
 	if ((op & UNLOGGED_RELATION_CLEANUP) != 0)
 	{
 		HTAB	   *hash;
-		HASHCTL		ctl;
 
 		/*
 		 * It's possible that someone could create a ton of unlogged relations
@@ -184,11 +183,8 @@ ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op)
 		 * need to be reset.  Otherwise, this cleanup operation would be
 		 * O(n^2).
 		 */
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(unlogged_relation_entry);
-		ctl.hcxt = CurrentMemoryContext;
-		hash = hash_create("unlogged relation OIDs", 32, &ctl,
-						   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		hash = hash_make(unlogged_relation_entry, relnumber,
+						 "unlogged relation OIDs", 32);
 
 		/* Scan the directory. */
 		dbspace_dir = AllocateDir(dbspacedirname);
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index ee3408df301..2d4c518dec2 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -284,8 +284,6 @@ ShmemAddrIsValid(const void *addr)
 void
 InitShmemIndex(void)
 {
-	HASHCTL		info;
-
 	/*
 	 * Create the shared memory shmem index.
 	 *
@@ -294,13 +292,8 @@ InitShmemIndex(void)
 	 * initializing the ShmemIndex itself.  The special "ShmemIndex" hash
 	 * table name will tell ShmemInitStruct to fake it.
 	 */
-	info.keysize = SHMEM_INDEX_KEYSIZE;
-	info.entrysize = sizeof(ShmemIndexEnt);
-
-	ShmemIndex = ShmemInitHash("ShmemIndex",
-							   SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE,
-							   &info,
-							   HASH_ELEM | HASH_STRINGS);
+	ShmemIndex = shmem_hash_make(ShmemIndexEnt, key, "ShmemIndex",
+								 SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE);
 }
 
 /*
@@ -369,6 +362,28 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 	return hash_create(name, init_size, infoP, hash_flags);
 }
 
+/*
+ * Implementation function for shmem_hash_make macros.
+ *
+ * Creates a shared memory hash table with simplified parameters.
+ * Pass NULL for opts to use all defaults.
+ */
+HTAB *
+shmem_hash_make_impl(const char *name, int64 init_size, int64 max_size,
+					 Size keysize, Size entrysize, bool string_key,
+					 const HASHOPTS *opts)
+{
+	HASHCTL		ctl;
+	int			flags;
+
+	/* Shared memory hash tables use ShmemAllocNoError, not a custom allocator */
+	Assert(opts == NULL || opts->alloc == NULL);
+
+	hash_opts_init(&ctl, &flags, keysize, entrysize, string_key, opts);
+
+	return ShmemInitHash(name, init_size, max_size, &ctl, flags);
+}
+
 /*
  * ShmemInitStruct -- Create/attach to a structure in shared memory.
  *
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 4222bdab078..9305e009772 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -32,6 +32,7 @@
 #include "storage/standby.h"
 #include "utils/hsearch.h"
 #include "utils/injection_point.h"
+#include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
@@ -95,7 +96,6 @@ void
 InitRecoveryTransactionEnvironment(void)
 {
 	VirtualTransactionId vxid;
-	HASHCTL		hash_ctl;
 
 	Assert(RecoveryLockHash == NULL);	/* don't run this twice */
 
@@ -103,18 +103,12 @@ InitRecoveryTransactionEnvironment(void)
 	 * Initialize the hash tables for tracking the locks held by each
 	 * transaction.
 	 */
-	hash_ctl.keysize = sizeof(xl_standby_lock);
-	hash_ctl.entrysize = sizeof(RecoveryLockEntry);
-	RecoveryLockHash = hash_create("RecoveryLockHash",
-								   64,
-								   &hash_ctl,
-								   HASH_ELEM | HASH_BLOBS);
-	hash_ctl.keysize = sizeof(TransactionId);
-	hash_ctl.entrysize = sizeof(RecoveryLockXidEntry);
-	RecoveryLockXidHash = hash_create("RecoveryLockXidHash",
-									  64,
-									  &hash_ctl,
-									  HASH_ELEM | HASH_BLOBS);
+	RecoveryLockHash = hash_make_cxt(RecoveryLockEntry, key,
+									 "RecoveryLockHash", 64,
+									 TopMemoryContext);
+	RecoveryLockXidHash = hash_make_cxt(RecoveryLockXidEntry, xid,
+										"RecoveryLockXidHash", 64,
+										TopMemoryContext);
 
 	/*
 	 * Initialize shared invalidation management for Startup process, being
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 9cb78ead105..e5ea54e7824 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -443,7 +443,7 @@ static void GetSingleProcBlockerStatusData(PGPROC *blocked_proc,
 void
 LockManagerShmemInit(void)
 {
-	HASHCTL		info;
+	HASHOPTS	opts;
 	int64		init_table_size,
 				max_table_size;
 	bool		found;
@@ -459,15 +459,11 @@ LockManagerShmemInit(void)
 	 * Allocate hash table for LOCK structs.  This stores per-locked-object
 	 * information.
 	 */
-	info.keysize = sizeof(LOCKTAG);
-	info.entrysize = sizeof(LOCK);
-	info.num_partitions = NUM_LOCK_PARTITIONS;
-
-	LockMethodLockHash = ShmemInitHash("LOCK hash",
-									   init_table_size,
-									   max_table_size,
-									   &info,
-									   HASH_ELEM | HASH_BLOBS | HASH_PARTITION);
+	MemSet(&opts, 0, sizeof(opts));
+	opts.num_partitions = NUM_LOCK_PARTITIONS;
+	LockMethodLockHash = shmem_hash_make_ext(LOCK, tag, "LOCK hash",
+											 init_table_size, max_table_size,
+											 &opts);
 
 	/* Assume an average of 2 holders per lock */
 	max_table_size *= 2;
@@ -477,16 +473,12 @@ LockManagerShmemInit(void)
 	 * Allocate hash table for PROCLOCK structs.  This stores
 	 * per-lock-per-holder information.
 	 */
-	info.keysize = sizeof(PROCLOCKTAG);
-	info.entrysize = sizeof(PROCLOCK);
-	info.hash = proclock_hash;
-	info.num_partitions = NUM_LOCK_PARTITIONS;
-
-	LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
-										   init_table_size,
-										   max_table_size,
-										   &info,
-										   HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
+	MemSet(&opts, 0, sizeof(opts));
+	opts.hash = proclock_hash;
+	opts.num_partitions = NUM_LOCK_PARTITIONS;
+	LockMethodProcLockHash = shmem_hash_make_ext(PROCLOCK, tag, "PROCLOCK hash",
+												 init_table_size, max_table_size,
+												 &opts);
 
 	/*
 	 * Allocate fast-path structures.
@@ -508,15 +500,9 @@ InitLockManagerAccess(void)
 	 * Allocate non-shared hash table for LOCALLOCK structs.  This stores lock
 	 * counts and resource owner information.
 	 */
-	HASHCTL		info;
-
-	info.keysize = sizeof(LOCALLOCKTAG);
-	info.entrysize = sizeof(LOCALLOCK);
-
-	LockMethodLocalHash = hash_create("LOCALLOCK hash",
-									  16,
-									  &info,
-									  HASH_ELEM | HASH_BLOBS);
+	LockMethodLocalHash = hash_make_cxt(LOCALLOCK, tag,
+										"LOCALLOCK hash", 16,
+										TopMemoryContext);
 }

@@ -3396,20 +3382,13 @@ CheckForSessionAndXactLocks(void)
 		bool		xactLock;	/* is any lockmode held at xact level? */
 	} PerLockTagEntry;
 
-	HASHCTL		hash_ctl;
 	HTAB	   *lockhtab;
 	HASH_SEQ_STATUS status;
 	LOCALLOCK  *locallock;
 
 	/* Create a local hash table keyed by LOCKTAG only */
-	hash_ctl.keysize = sizeof(LOCKTAG);
-	hash_ctl.entrysize = sizeof(PerLockTagEntry);
-	hash_ctl.hcxt = CurrentMemoryContext;
-
-	lockhtab = hash_create("CheckForSessionAndXactLocks table",
-						   256, /* arbitrary initial size */
-						   &hash_ctl,
-						   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	lockhtab = hash_make(PerLockTagEntry, lock,
+						 "CheckForSessionAndXactLocks table", 256);
 
 	/* Scan local lock table to find entries for each LOCKTAG */
 	hash_seq_init(&status, LockMethodLocalHash);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 255cfa8fa95..1f749beadce 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -285,7 +285,6 @@ static lwlock_stats * get_lwlock_stats_entry(LWLock *lock);
 static void
 init_lwlock_stats(void)
 {
-	HASHCTL		ctl;
 	static MemoryContext lwlock_stats_cxt = NULL;
 	static bool exit_registered = false;
 
@@ -305,11 +304,8 @@ init_lwlock_stats(void)
 											 ALLOCSET_DEFAULT_SIZES);
 	MemoryContextAllowInCriticalSection(lwlock_stats_cxt, true);
 
-	ctl.keysize = sizeof(lwlock_stats_key);
-	ctl.entrysize = sizeof(lwlock_stats);
-	ctl.hcxt = lwlock_stats_cxt;
-	lwlock_stats_htab = hash_create("lwlock stats", 16384, &ctl,
-									HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	lwlock_stats_htab = hash_make_cxt(lwlock_stats, key,
+									  "lwlock stats", 16384, lwlock_stats_cxt);
 	if (!exit_registered)
 	{
 		on_shmem_exit(print_lwlock_stats, 0);
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index f12f8f77aad..c2d85032716 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -212,6 +212,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/guc_hooks.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -1144,7 +1145,7 @@ CheckPointPredicate(void)
 void
 PredicateLockShmemInit(void)
 {
-	HASHCTL		info;
+	HASHOPTS	opts;
 	int64		max_table_size;
 	Size		requestSize;
 	bool		found;
@@ -1163,16 +1164,13 @@ PredicateLockShmemInit(void)
 	 * Allocate hash table for PREDICATELOCKTARGET structs.  This stores
 	 * per-predicate-lock-target information.
 	 */
-	info.keysize = sizeof(PREDICATELOCKTARGETTAG);
-	info.entrysize = sizeof(PREDICATELOCKTARGET);
-	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
-
-	PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash",
-											max_table_size,
-											max_table_size,
-											&info,
-											HASH_ELEM | HASH_BLOBS |
-											HASH_PARTITION | HASH_FIXED_SIZE);
+	MemSet(&opts, 0, sizeof(opts));
+	opts.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
+	opts.fixed_size = true;
+	PredicateLockTargetHash = shmem_hash_make_ext(PREDICATELOCKTARGET, tag,
+												  "PREDICATELOCKTARGET hash",
+												  max_table_size, max_table_size,
+												  &opts);
 
 	/*
 	 * Reserve a dummy entry in the hash table; we use it to make sure there's
@@ -1195,20 +1193,17 @@ PredicateLockShmemInit(void)
 	 * Allocate hash table for PREDICATELOCK structs.  This stores per
 	 * xact-lock-of-a-target information.
 	 */
-	info.keysize = sizeof(PREDICATELOCKTAG);
-	info.entrysize = sizeof(PREDICATELOCK);
-	info.hash = predicatelock_hash;
-	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
-
 	/* Assume an average of 2 xacts per target */
 	max_table_size *= 2;
 
-	PredicateLockHash = ShmemInitHash("PREDICATELOCK hash",
-									  max_table_size,
-									  max_table_size,
-									  &info,
-									  HASH_ELEM | HASH_FUNCTION |
-									  HASH_PARTITION | HASH_FIXED_SIZE);
+	MemSet(&opts, 0, sizeof(opts));
+	opts.hash = predicatelock_hash;
+	opts.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
+	opts.fixed_size = true;
+	PredicateLockHash = shmem_hash_make_ext(PREDICATELOCK, tag,
+											"PREDICATELOCK hash",
+											max_table_size, max_table_size,
+											&opts);
 
 	/*
 	 * Compute size for serializable transaction hashtable. Note these
@@ -1282,15 +1277,12 @@ PredicateLockShmemInit(void)
 	 * Allocate hash table for SERIALIZABLEXID structs.  This stores per-xid
 	 * information for serializable transactions which have accessed data.
 	 */
-	info.keysize = sizeof(SERIALIZABLEXIDTAG);
-	info.entrysize = sizeof(SERIALIZABLEXID);
-
-	SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash",
-										max_table_size,
-										max_table_size,
-										&info,
-										HASH_ELEM | HASH_BLOBS |
-										HASH_FIXED_SIZE);
+	MemSet(&opts, 0, sizeof(opts));
+	opts.fixed_size = true;
+	SerializableXidHash = shmem_hash_make_ext(SERIALIZABLEXID, tag,
+											  "SERIALIZABLEXID hash",
+											  max_table_size, max_table_size,
+											  &opts);
 
 	/*
 	 * Allocate space for tracking rw-conflicts in lists attached to the
@@ -1939,16 +1931,12 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
 static void
 CreateLocalPredicateLockHash(void)
 {
-	HASHCTL		hash_ctl;
-
 	/* Initialize the backend-local hash table of parent locks */
 	Assert(LocalPredicateLockHash == NULL);
-	hash_ctl.keysize = sizeof(PREDICATELOCKTARGETTAG);
-	hash_ctl.entrysize = sizeof(LOCALPREDICATELOCK);
-	LocalPredicateLockHash = hash_create("Local predicate lock",
-										 max_predicate_locks_per_xact,
-										 &hash_ctl,
-										 HASH_ELEM | HASH_BLOBS);
+	LocalPredicateLockHash = hash_make_cxt(LOCALPREDICATELOCK, tag,
+										   "Local predicate lock",
+										   max_predicate_locks_per_xact,
+										   TopMemoryContext);
 }
 
 /*
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index bce37a36d51..d4fa6a144f1 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -73,6 +73,7 @@
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
+#include "utils/memutils.h"

 /*
@@ -250,12 +251,9 @@ smgropen(RelFileLocator rlocator, ProcNumber backend)
 	if (SMgrRelationHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(RelFileLocatorBackend);
-		ctl.entrysize = sizeof(SMgrRelationData);
-		SMgrRelationHash = hash_create("smgr relation table", 400,
-									   &ctl, HASH_ELEM | HASH_BLOBS);
+		SMgrRelationHash = hash_make_cxt(SMgrRelationData, smgr_rlocator,
+										 "smgr relation table", 400,
+										 TopMemoryContext);
 		dlist_init(&unpinned_relns);
 	}
 
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index fc16db90133..42b7fc11288 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -130,8 +130,6 @@ InitSync(void)
 	 */
 	if (!IsUnderPostmaster || AmCheckpointerProcess())
 	{
-		HASHCTL		hash_ctl;
-
 		/*
 		 * XXX: The checkpointer needs to add entries to the pending ops table
 		 * when absorbing fsync requests.  That is done within a critical
@@ -146,13 +144,8 @@ InitSync(void)
 											  ALLOCSET_DEFAULT_SIZES);
 		MemoryContextAllowInCriticalSection(pendingOpsCxt, true);
 
-		hash_ctl.keysize = sizeof(FileTag);
-		hash_ctl.entrysize = sizeof(PendingFsyncEntry);
-		hash_ctl.hcxt = pendingOpsCxt;
-		pendingOps = hash_create("Pending Ops Table",
-								 100L,
-								 &hash_ctl,
-								 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		pendingOps = hash_make_cxt(PendingFsyncEntry, tag,
+								   "Pending Ops Table", 100L, pendingOpsCxt);
 		pendingUnlinks = NIL;
 	}
 }
diff --git a/src/backend/tsearch/ts_typanalyze.c b/src/backend/tsearch/ts_typanalyze.c
index 93aab00a3ca..a9bc6901a12 100644
--- a/src/backend/tsearch/ts_typanalyze.c
+++ b/src/backend/tsearch/ts_typanalyze.c
@@ -149,7 +149,6 @@ compute_tsvector_stats(VacAttrStats *stats,
 
 	/* This is D from the LC algorithm. */
 	HTAB	   *lexemes_tab;
-	HASHCTL		hash_ctl;
 	HASH_SEQ_STATUS scan_status;
 
 	/* This is the current bucket number from the LC algorithm */
@@ -180,15 +179,9 @@ compute_tsvector_stats(VacAttrStats *stats,
 	 * worry about overflowing the initial size. Also we don't need to pay any
 	 * attention to locking and memory management.
 	 */
-	hash_ctl.keysize = sizeof(LexemeHashKey);
-	hash_ctl.entrysize = sizeof(TrackItem);
-	hash_ctl.hash = lexeme_hash;
-	hash_ctl.match = lexeme_match;
-	hash_ctl.hcxt = CurrentMemoryContext;
-	lexemes_tab = hash_create("Analyzed lexemes table",
-							  num_mcelem,
-							  &hash_ctl,
-							  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+	lexemes_tab = hash_make_fn(TrackItem, key,
+							   "Analyzed lexemes table", num_mcelem,
+							   lexeme_hash, lexeme_match);
 
 	/* Initialize counters. */
 	b_current = 1;
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index d9b8f34a355..56a62029abb 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -119,7 +119,6 @@ void
 WaitEventCustomShmemInit(void)
 {
 	bool		found;
-	HASHCTL		info;
 
 	WaitEventCustomCounter = (WaitEventCustomCounterData *)
 		ShmemInitStruct("WaitEventCustomCounterData",
@@ -133,24 +132,18 @@ WaitEventCustomShmemInit(void)
 	}
 
 	/* initialize or attach the hash tables to store custom wait events */
-	info.keysize = sizeof(uint32);
-	info.entrysize = sizeof(WaitEventCustomEntryByInfo);
 	WaitEventCustomHashByInfo =
-		ShmemInitHash("WaitEventCustom hash by wait event information",
-					  WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
-					  WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
-					  &info,
-					  HASH_ELEM | HASH_BLOBS);
+		shmem_hash_make(WaitEventCustomEntryByInfo, wait_event_info,
+						"WaitEventCustom hash by wait event information",
+						WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
+						WAIT_EVENT_CUSTOM_HASH_MAX_SIZE);
 
 	/* key is a NULL-terminated string */
-	info.keysize = sizeof(char[NAMEDATALEN]);
-	info.entrysize = sizeof(WaitEventCustomEntryByName);
 	WaitEventCustomHashByName =
-		ShmemInitHash("WaitEventCustom hash by name",
-					  WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
-					  WAIT_EVENT_CUSTOM_HASH_MAX_SIZE,
-					  &info,
-					  HASH_ELEM | HASH_STRINGS);
+		shmem_hash_make(WaitEventCustomEntryByName, wait_event_name,
+						"WaitEventCustom hash by name",
+						WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
+						WAIT_EVENT_CUSTOM_HASH_MAX_SIZE);
 }
 
 /*
diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c
index 560b27f3ca7..cc310c044b3 100644
--- a/src/backend/utils/adt/array_typanalyze.c
+++ b/src/backend/utils/adt/array_typanalyze.c
@@ -223,7 +223,6 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 
 	/* This is D from the LC algorithm. */
 	HTAB	   *elements_tab;
-	HASHCTL		elem_hash_ctl;
 	HASH_SEQ_STATUS scan_status;
 
 	/* This is the current bucket number from the LC algorithm */
@@ -236,7 +235,6 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 	TrackItem  *item;
 	int			slot_idx;
 	HTAB	   *count_tab;
-	HASHCTL		count_hash_ctl;
 	DECountItem *count_item;
 
 	extra_data = (ArrayAnalyzeExtraData *) stats->extra_data;
@@ -276,24 +274,13 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 	 * worry about overflowing the initial size. Also we don't need to pay any
 	 * attention to locking and memory management.
 	 */
-	elem_hash_ctl.keysize = sizeof(Datum);
-	elem_hash_ctl.entrysize = sizeof(TrackItem);
-	elem_hash_ctl.hash = element_hash;
-	elem_hash_ctl.match = element_match;
-	elem_hash_ctl.hcxt = CurrentMemoryContext;
-	elements_tab = hash_create("Analyzed elements table",
-							   num_mcelem,
-							   &elem_hash_ctl,
-							   HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+	elements_tab = hash_make_fn(TrackItem, key,
+								"Analyzed elements table", num_mcelem,
+								element_hash, element_match);
 
 	/* hashtable for array distinct elements counts */
-	count_hash_ctl.keysize = sizeof(int);
-	count_hash_ctl.entrysize = sizeof(DECountItem);
-	count_hash_ctl.hcxt = CurrentMemoryContext;
-	count_tab = hash_create("Array distinct element count table",
-							64,
-							&count_hash_ctl,
-							HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	count_tab = hash_make(DECountItem, count,
+						  "Array distinct element count table", 64);
 
 	/* Initialize counters. */
 	b_current = 1;
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 06dd62f0008..56aa88e495e 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -933,19 +933,8 @@ json_unique_hash_match(const void *key1, const void *key2, Size keysize)
 static void
 json_unique_check_init(JsonUniqueCheckState *cxt)
 {
-	HASHCTL		ctl;
-
-	memset(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(JsonUniqueHashEntry);
-	ctl.entrysize = sizeof(JsonUniqueHashEntry);
-	ctl.hcxt = CurrentMemoryContext;
-	ctl.hash = json_unique_hash;
-	ctl.match = json_unique_hash_match;
-
-	*cxt = hash_create("json object hashtable",
-					   32,
-					   &ctl,
-					   HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
+	*cxt = hashset_make_fn(JsonUniqueHashEntry, "json object hashtable", 32,
+						   json_unique_hash, json_unique_hash_match);
 }
 
 static void
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index de32e329975..81e30d79437 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3811,18 +3811,12 @@ static HTAB *
 get_json_object_as_hash(const char *json, int len, const char *funcname,
 						Node *escontext)
 {
-	HASHCTL		ctl;
 	HTAB	   *tab;
 	JHashState *state;
 	JsonSemAction *sem;
 
-	ctl.keysize = NAMEDATALEN;
-	ctl.entrysize = sizeof(JsonHashEntry);
-	ctl.hcxt = CurrentMemoryContext;
-	tab = hash_create("json object hashtable",
-					  100,
-					  &ctl,
-					  HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+	tab = hash_make(JsonHashEntry, fname,
+					"json object hashtable", 100);
 
 	state = palloc0(sizeof(JHashState));
 	sem = palloc0(sizeof(JsonSemAction));
@@ -4216,7 +4210,6 @@ populate_recordset_object_start(void *state)
 {
 	PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
 	int			lex_level = _state->lex->lex_level;
-	HASHCTL		ctl;
 
 	/* Reject object at top level: we must have an array at level 0 */
 	if (lex_level == 0)
@@ -4230,13 +4223,8 @@ populate_recordset_object_start(void *state)
 		return JSON_SUCCESS;
 
 	/* Object at level 1: set up a new hash table for this object */
-	ctl.keysize = NAMEDATALEN;
-	ctl.entrysize = sizeof(JsonHashEntry);
-	ctl.hcxt = CurrentMemoryContext;
-	_state->json_hash = hash_create("json object hashtable",
-									100,
-									&ctl,
-									HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+	_state->json_hash = hash_make(JsonHashEntry, fname,
+								  "json object hashtable", 100);
 
 	return JSON_SUCCESS;
 }
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index fe6dce9cba3..84b43f5c4e0 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -185,17 +185,10 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	int			context_id;
 	List	   *contexts;
-	HASHCTL		ctl;
 	HTAB	   *context_id_lookup;
 
-	ctl.keysize = sizeof(MemoryContext);
-	ctl.entrysize = sizeof(MemoryContextId);
-	ctl.hcxt = CurrentMemoryContext;
-
-	context_id_lookup = hash_create("pg_get_backend_memory_contexts",
-									256,
-									&ctl,
-									HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	context_id_lookup = hash_make(MemoryContextId, context,
+								  "pg_get_backend_memory_contexts", 256);
 
 	InitMaterializedSRF(fcinfo, 0);
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..e0f05f2cc4a 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2857,30 +2857,25 @@ ri_NullCheck(TupleDesc tupDesc,
 static void
 ri_InitHashTables(void)
 {
-	HASHCTL		ctl;
-
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RI_ConstraintInfo);
-	ri_constraint_cache = hash_create("RI constraint cache",
-									  RI_INIT_CONSTRAINTHASHSIZE,
-									  &ctl, HASH_ELEM | HASH_BLOBS);
+	ri_constraint_cache = hash_make_cxt(RI_ConstraintInfo, constraint_id,
+										"RI constraint cache",
+										RI_INIT_CONSTRAINTHASHSIZE,
+										TopMemoryContext);
 
 	/* Arrange to flush cache on pg_constraint changes */
 	CacheRegisterSyscacheCallback(CONSTROID,
 								  InvalidateConstraintCacheCallBack,
 								  (Datum) 0);
 
-	ctl.keysize = sizeof(RI_QueryKey);
-	ctl.entrysize = sizeof(RI_QueryHashEntry);
-	ri_query_cache = hash_create("RI query cache",
-								 RI_INIT_QUERYHASHSIZE,
-								 &ctl, HASH_ELEM | HASH_BLOBS);
-
-	ctl.keysize = sizeof(RI_CompareKey);
-	ctl.entrysize = sizeof(RI_CompareHashEntry);
-	ri_compare_cache = hash_create("RI compare cache",
+	ri_query_cache = hash_make_cxt(RI_QueryHashEntry, key,
+								   "RI query cache",
 								   RI_INIT_QUERYHASHSIZE,
-								   &ctl, HASH_ELEM | HASH_BLOBS);
+								   TopMemoryContext);
+
+	ri_compare_cache = hash_make_cxt(RI_CompareHashEntry, key,
+									 "RI compare cache",
+									 RI_INIT_QUERYHASHSIZE,
+									 TopMemoryContext);
 }

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6cf90be40bb..98d331c290f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3887,7 +3887,6 @@ static void
 set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 				 Bitmapset *rels_used)
 {
-	HASHCTL		hash_ctl;
 	HTAB	   *names_hash;
 	NameHashEntry *hentry;
 	bool		found;
@@ -3903,13 +3902,9 @@ set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 	 * We use a hash table to hold known names, so that this process is O(N)
 	 * not O(N^2) for N names.
 	 */
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = sizeof(NameHashEntry);
-	hash_ctl.hcxt = CurrentMemoryContext;
-	names_hash = hash_create("set_rtable_names names",
-							 list_length(dpns->rtable),
-							 &hash_ctl,
-							 HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+	names_hash = hash_make(NameHashEntry, name,
+						   "set_rtable_names names",
+						   list_length(dpns->rtable));
 
 	/* Preload the hash table with names appearing in parent_namespaces */
 	foreach(lc, parent_namespaces)
@@ -4980,7 +4975,6 @@ expand_colnames_array_to(deparse_columns *colinfo, int n)
 static void
 build_colinfo_names_hash(deparse_columns *colinfo)
 {
-	HASHCTL		hash_ctl;
 	int			i;
 	ListCell   *lc;
 
@@ -4996,13 +4990,10 @@ build_colinfo_names_hash(deparse_columns *colinfo)
 	 * Set up the hash table.  The entries are just strings with no other
 	 * payload.
 	 */
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = NAMEDATALEN;
-	hash_ctl.hcxt = CurrentMemoryContext;
-	colinfo->names_hash = hash_create("deparse_columns names",
-									  colinfo->num_cols + colinfo->num_new_cols,
-									  &hash_ctl,
-									  HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+	colinfo->names_hash =
+		hashset_make_cxt(NameData, "deparse_columns names",
+						 colinfo->num_cols + colinfo->num_new_cols,
+						 CurrentMemoryContext);
 
 	/*
 	 * Preload the hash table with any names already present (these would have
diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c
index 45d1e2be007..caae39e3bb1 100644
--- a/src/backend/utils/cache/attoptcache.c
+++ b/src/backend/utils/cache/attoptcache.c
@@ -21,6 +21,7 @@
 #include "utils/catcache.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 #include "varatt.h"
 
@@ -96,22 +97,15 @@ relatt_cache_syshash(const void *key, Size keysize)
 static void
 InitializeAttoptCache(void)
 {
-	HASHCTL		ctl;
-
-	/* Initialize the hash table. */
-	ctl.keysize = sizeof(AttoptCacheKey);
-	ctl.entrysize = sizeof(AttoptCacheEntry);
-
 	/*
 	 * AttoptCacheEntry takes hash value from the system cache. For
 	 * AttoptCacheHash we use the same hash in order to speedup search by hash
 	 * value. This is used by hash_seq_init_with_hash_value().
 	 */
-	ctl.hash = relatt_cache_syshash;
-
-	AttoptCacheHash =
-		hash_create("Attopt cache", 256, &ctl,
-					HASH_ELEM | HASH_FUNCTION);
+	AttoptCacheHash = hash_make_fn_cxt(AttoptCacheEntry, key,
+									   "Attopt cache", 256,
+									   relatt_cache_syshash, NULL,
+									   TopMemoryContext);
 
 	/* Make sure we've initialized CacheMemoryContext. */
 	if (!CacheMemoryContext)
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index b9d5a5998be..64ff7568874 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -76,7 +76,6 @@ EventCacheLookup(EventTriggerEvent event)
 static void
 BuildEventTriggerCache(void)
 {
-	HASHCTL		ctl;
 	HTAB	   *cache;
 	Relation	rel;
 	Relation	irel;
@@ -113,11 +112,9 @@ BuildEventTriggerCache(void)
 	EventTriggerCacheState = ETCS_REBUILD_STARTED;
 
 	/* Create new hash table. */
-	ctl.keysize = sizeof(EventTriggerEvent);
-	ctl.entrysize = sizeof(EventTriggerCacheEntry);
-	ctl.hcxt = EventTriggerCacheContext;
-	cache = hash_create("EventTriggerCacheHash", 32, &ctl,
-						HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	cache = hash_make_cxt(EventTriggerCacheEntry, event,
+						  "EventTriggerCacheHash", 32,
+						  EventTriggerCacheContext);
 
 	/*
 	 * Prepare to scan pg_event_trigger in name order.
diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c
index afc048a051e..34438a9f93b 100644
--- a/src/backend/utils/cache/funccache.c
+++ b/src/backend/utils/cache/funccache.c
@@ -58,19 +58,13 @@ static int	cfunc_match(const void *key1, const void *key2, Size keysize);
 static void
 cfunc_hashtable_init(void)
 {
-	HASHCTL		ctl;
-
 	/* don't allow double-initialization */
 	Assert(cfunc_hashtable == NULL);
 
-	ctl.keysize = sizeof(CachedFunctionHashKey);
-	ctl.entrysize = sizeof(CachedFunctionHashEntry);
-	ctl.hash = cfunc_hash;
-	ctl.match = cfunc_match;
-	cfunc_hashtable = hash_create("Cached function hash",
-								  FUNCS_PER_USER,
-								  &ctl,
-								  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+	cfunc_hashtable = hash_make_fn_cxt(CachedFunctionHashEntry, key,
+									   "Cached function hash", FUNCS_PER_USER,
+									   cfunc_hash, cfunc_match,
+									   TopMemoryContext);
 }
 
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..b76d6bbd5c9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1677,17 +1677,14 @@ LookupOpclassInfo(Oid operatorClassOid,
 
 	if (OpClassCache == NULL)
 	{
-		/* First time through: initialize the opclass cache */
-		HASHCTL		ctl;
-
 		/* Also make sure CacheMemoryContext exists */
 		if (!CacheMemoryContext)
 			CreateCacheMemoryContext();
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(OpClassCacheEnt);
-		OpClassCache = hash_create("Operator class cache", 64,
-								   &ctl, HASH_ELEM | HASH_BLOBS);
+		/* First time through: initialize the opclass cache */
+		OpClassCache = hash_make_cxt(OpClassCacheEnt, opclassoid,
+									 "Operator class cache", 64,
+									 TopMemoryContext);
 	}
 
 	opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
@@ -4001,7 +3998,6 @@ RelationAssumeNewRelfilelocator(Relation relation)
 void
 RelationCacheInitialize(void)
 {
-	HASHCTL		ctl;
 	int			allocsize;
 
 	/*
@@ -4013,10 +4009,9 @@ RelationCacheInitialize(void)
 	/*
 	 * create hashtable that indexes the relcache
 	 */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RelIdCacheEnt);
-	RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE,
-								  &ctl, HASH_ELEM | HASH_BLOBS);
+	RelationIdCache = hash_make_cxt(RelIdCacheEnt, reloid,
+									"Relcache by OID", INITRELCACHESIZE,
+									TopMemoryContext);
 
 	/*
 	 * reserve enough in_progress_list slots for many cases
diff --git a/src/backend/utils/cache/relfilenumbermap.c b/src/backend/utils/cache/relfilenumbermap.c
index 0b6f9cf3fa1..69aba463eae 100644
--- a/src/backend/utils/cache/relfilenumbermap.c
+++ b/src/backend/utils/cache/relfilenumbermap.c
@@ -85,7 +85,6 @@ RelfilenumberMapInvalidateCallback(Datum arg, Oid relid)
 static void
 InitializeRelfilenumberMap(void)
 {
-	HASHCTL		ctl;
 	int			i;
 
 	/* Make sure we've initialized CacheMemoryContext. */
@@ -113,13 +112,9 @@ InitializeRelfilenumberMap(void)
 	 * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
 	 * error.
 	 */
-	ctl.keysize = sizeof(RelfilenumberMapKey);
-	ctl.entrysize = sizeof(RelfilenumberMapEntry);
-	ctl.hcxt = CacheMemoryContext;
-
 	RelfilenumberMapHash =
-		hash_create("RelfilenumberMap cache", 64, &ctl,
-					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		hash_make_cxt(RelfilenumberMapEntry, key,
+					  "RelfilenumberMap cache", 64, CacheMemoryContext);
 
 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index 23458599298..bcfd6a9f0d2 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -27,6 +27,7 @@
 #include "utils/catcache.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
+#include "utils/memutils.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 #include "varatt.h"
@@ -77,14 +78,10 @@ InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 static void
 InitializeTableSpaceCache(void)
 {
-	HASHCTL		ctl;
-
 	/* Initialize the hash table. */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(TableSpaceCacheEntry);
 	TableSpaceCacheHash =
-		hash_create("TableSpace cache", 16, &ctl,
-					HASH_ELEM | HASH_BLOBS);
+		hash_make_cxt(TableSpaceCacheEntry, oid,
+					  "TableSpace cache", 16, TopMemoryContext);
 
 	/* Make sure we've initialized CacheMemoryContext. */
 	if (!CacheMemoryContext)
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index e8ae53238d0..cfa6d8b09a4 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -117,12 +117,9 @@ lookup_ts_parser_cache(Oid prsId)
 	if (TSParserCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TSParserCacheEntry);
-		TSParserCacheHash = hash_create("Tsearch parser cache", 4,
-										&ctl, HASH_ELEM | HASH_BLOBS);
+		TSParserCacheHash = hash_make_cxt(TSParserCacheEntry, prsId,
+										  "Tsearch parser cache", 4,
+										  TopMemoryContext);
 		/* Flush cache on pg_ts_parser changes */
 		CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
 									  PointerGetDatum(TSParserCacheHash));
@@ -212,12 +209,9 @@ lookup_ts_dictionary_cache(Oid dictId)
 	if (TSDictionaryCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TSDictionaryCacheEntry);
-		TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
-											&ctl, HASH_ELEM | HASH_BLOBS);
+		TSDictionaryCacheHash = hash_make_cxt(TSDictionaryCacheEntry, dictId,
+											  "Tsearch dictionary cache", 8,
+											  TopMemoryContext);
 		/* Flush cache on pg_ts_dict and pg_ts_template changes */
 		CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
 									  PointerGetDatum(TSDictionaryCacheHash));
@@ -363,12 +357,9 @@ lookup_ts_dictionary_cache(Oid dictId)
 static void
 init_ts_config_cache(void)
 {
-	HASHCTL		ctl;
-
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(TSConfigCacheEntry);
-	TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
-									&ctl, HASH_ELEM | HASH_BLOBS);
+	TSConfigCacheHash = hash_make_cxt(TSConfigCacheEntry, cfgId,
+									  "Tsearch configuration cache", 16,
+									  TopMemoryContext);
 	/* Flush cache on pg_ts_config and pg_ts_config_map changes */
 	CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
 								  PointerGetDatum(TSConfigCacheHash));
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 6a347698edf..7e4ffbe6708 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -392,28 +392,23 @@ lookup_type_cache(Oid type_id, int flags)
 	if (TypeCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
 		int			allocsize;
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TypeCacheEntry);
-
 		/*
 		 * TypeCacheEntry takes hash value from the system cache. For
 		 * TypeCacheHash we use the same hash in order to speedup search by
 		 * hash value. This is used by hash_seq_init_with_hash_value().
 		 */
-		ctl.hash = type_cache_syshash;
-
-		TypeCacheHash = hash_create("Type information cache", 64,
-									&ctl, HASH_ELEM | HASH_FUNCTION);
+		TypeCacheHash = hash_make_fn_cxt(TypeCacheEntry, type_id,
+										 "Type information cache", 64,
+										 type_cache_syshash, NULL,
+										 TopMemoryContext);
 
 		Assert(RelIdToTypeIdCacheHash == NULL);
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(RelIdToTypeIdCacheEntry);
-		RelIdToTypeIdCacheHash = hash_create("Map from relid to OID of cached composite type", 64,
-											 &ctl, HASH_ELEM | HASH_BLOBS);
+		RelIdToTypeIdCacheHash = hash_make_cxt(RelIdToTypeIdCacheEntry, relid,
+											   "Map from relid to OID of cached composite type",
+											   64, TopMemoryContext);
 
 		/* Also set up callbacks for SI invalidations */
 		CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
@@ -2050,15 +2045,11 @@ assign_record_type_typmod(TupleDesc tupDesc)
 	if (RecordCacheHash == NULL)
 	{
 		/* First time through: initialize the hash table */
-		HASHCTL		ctl;
-
-		ctl.keysize = sizeof(TupleDesc);	/* just the pointer */
-		ctl.entrysize = sizeof(RecordCacheEntry);
-		ctl.hash = record_type_typmod_hash;
-		ctl.match = record_type_typmod_compare;
-		RecordCacheHash = hash_create("Record information cache", 64,
-									  &ctl,
-									  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+		RecordCacheHash = hash_make_fn_cxt(RecordCacheEntry, tupdesc,
+										   "Record information cache", 64,
+										   record_type_typmod_hash,
+										   record_type_typmod_compare,
+										   TopMemoryContext);
 
 		/* Also make sure CacheMemoryContext exists */
 		if (!CacheMemoryContext)
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 1366521f471..f587bbb4cf9 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -26,6 +26,7 @@
 #include "storage/fd.h"
 #include "storage/shmem.h"
 #include "utils/hsearch.h"
+#include "utils/memutils.h"

 /* signature for PostgreSQL-specific library init function */
@@ -671,14 +672,9 @@ find_rendezvous_variable(const char *varName)
 	/* Create a hashtable if we haven't already done so in this process */
 	if (rendezvousHash == NULL)
 	{
-		HASHCTL		ctl;
-
-		ctl.keysize = NAMEDATALEN;
-		ctl.entrysize = sizeof(rendezvousHashEntry);
-		rendezvousHash = hash_create("Rendezvous variable hash",
-									 16,
-									 &ctl,
-									 HASH_ELEM | HASH_STRINGS);
+		rendezvousHash = hash_make_cxt(rendezvousHashEntry, varName,
+									   "Rendezvous variable hash", 16,
+									   TopMemoryContext);
 	}
 
 	/* Find or create the hashtable entry for this varName */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..fc85a05367c 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -32,6 +32,7 @@
 #include "utils/fmgrtab.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 
 /*
@@ -547,14 +548,8 @@ record_C_func(HeapTuple procedureTuple,
 	/* Create the hash table if it doesn't exist yet */
 	if (CFuncHash == NULL)
 	{
-		HASHCTL		hash_ctl;
-
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(CFuncHashTabEntry);
-		CFuncHash = hash_create("CFuncHash",
-								100,
-								&hash_ctl,
-								HASH_ELEM | HASH_BLOBS);
+		CFuncHash = hash_make_cxt(CFuncHashTabEntry, fn_oid,
+								  "CFuncHash", 100, TopMemoryContext);
 	}
 
 	entry = (CFuncHashTabEntry *)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c6484aea087..f3897c953d3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -871,7 +871,6 @@ build_guc_variables(void)
 {
 	int			size_vars;
 	int			num_vars = 0;
-	HASHCTL		hash_ctl;
 	GUCHashEntry *hentry;
 	bool		found;
 
@@ -894,15 +893,10 @@ build_guc_variables(void)
 	 */
 	size_vars = num_vars + num_vars / 4;
 
-	hash_ctl.keysize = sizeof(char *);
-	hash_ctl.entrysize = sizeof(GUCHashEntry);
-	hash_ctl.hash = guc_name_hash;
-	hash_ctl.match = guc_name_match;
-	hash_ctl.hcxt = GUCMemoryContext;
-	guc_hashtab = hash_create("GUC hash table",
-							  size_vars,
-							  &hash_ctl,
-							  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+	guc_hashtab = hash_make_fn_cxt(GUCHashEntry, gucname,
+								   "GUC hash table", size_vars,
+								   guc_name_hash, guc_name_match,
+								   GUCMemoryContext);
 
 	for (int i = 0; ConfigureNames[i].name; i++)
 	{
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index d02618c7ffe..5075399f017 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -127,16 +127,10 @@ injection_point_cache_add(const char *name,
 	/* If first time, initialize */
 	if (InjectionPointCache == NULL)
 	{
-		HASHCTL		hash_ctl;
-
-		hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
-		hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
-		hash_ctl.hcxt = TopMemoryContext;
-
-		InjectionPointCache = hash_create("InjectionPoint cache hash",
-										  MAX_INJECTION_POINTS,
-										  &hash_ctl,
-										  HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+		InjectionPointCache = hash_make_cxt(InjectionPointCacheEntry, name,
+											"InjectionPoint cache hash",
+											MAX_INJECTION_POINTS,
+											TopMemoryContext);
 	}
 
 	entry = (InjectionPointCacheEntry *)
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 943da087c9f..8241d32f0c6 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -103,23 +103,19 @@ static MemoryContext TopPortalContext = NULL;
 void
 EnablePortalManager(void)
 {
-	HASHCTL		ctl;
-
 	Assert(TopPortalContext == NULL);
 
 	TopPortalContext = AllocSetContextCreate(TopMemoryContext,
 											 "TopPortalContext",
 											 ALLOCSET_DEFAULT_SIZES);
 
-	ctl.keysize = MAX_PORTALNAME_LEN;
-	ctl.entrysize = sizeof(PortalHashEnt);
-
 	/*
 	 * use PORTALS_PER_USER as a guess of how many hash table entries to
 	 * create, initially
 	 */
-	PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
-								  &ctl, HASH_ELEM | HASH_STRINGS);
+	PortalHashTable = hash_make_cxt(PortalHashEnt, portalname,
+									"Portal hash", PORTALS_PER_USER,
+									TopMemoryContext);
 }
 
 /*
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 1e815571570..92662a63177 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -214,8 +214,6 @@ GetComboCommandId(CommandId cmin, CommandId cmax)
 	 */
 	if (comboHash == NULL)
 	{
-		HASHCTL		hash_ctl;
-
 		/* Make array first; existence of hash table asserts array exists */
 		comboCids = (ComboCidKeyData *)
 			MemoryContextAlloc(TopTransactionContext,
@@ -223,14 +221,9 @@ GetComboCommandId(CommandId cmin, CommandId cmax)
 		sizeComboCids = CCID_ARRAY_SIZE;
 		usedComboCids = 0;
 
-		hash_ctl.keysize = sizeof(ComboCidKeyData);
-		hash_ctl.entrysize = sizeof(ComboCidEntryData);
-		hash_ctl.hcxt = TopTransactionContext;
-
-		comboHash = hash_create("Combo CIDs",
-								CCID_HASH_SIZE,
-								&hash_ctl,
-								HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		comboHash = hash_make_cxt(ComboCidEntryData, key,
+								  "Combo CIDs", CCID_HASH_SIZE,
+								  TopTransactionContext);
 	}
 
 	/*
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 73ba1748fe0..e211f821edc 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -390,7 +390,6 @@ _PG_init(void)
 	 * "plperl.use_strict"
 	 */
 	static bool inited = false;
-	HASHCTL		hash_ctl;
 
 	if (inited)
 		return;
@@ -460,19 +459,13 @@ _PG_init(void)
 	/*
 	 * Create hash tables.
 	 */
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(plperl_interp_desc);
-	plperl_interp_hash = hash_create("PL/Perl interpreters",
-									 8,
-									 &hash_ctl,
-									 HASH_ELEM | HASH_BLOBS);
-
-	hash_ctl.keysize = sizeof(plperl_proc_key);
-	hash_ctl.entrysize = sizeof(plperl_proc_ptr);
-	plperl_proc_hash = hash_create("PL/Perl procedures",
-								   32,
-								   &hash_ctl,
-								   HASH_ELEM | HASH_BLOBS);
+	plperl_interp_hash = hash_make_cxt(plperl_interp_desc, user_id,
+									   "PL/Perl interpreters", 8,
+									   TopMemoryContext);
+
+	plperl_proc_hash = hash_make_cxt(plperl_proc_ptr, proc_key,
+									 "PL/Perl procedures", 32,
+									 TopMemoryContext);
 
 	/*
 	 * Save the default opmask.
@@ -578,14 +571,9 @@ select_perl_context(bool trusted)
 	/* Make sure we have a query_hash for this interpreter */
 	if (interp_desc->query_hash == NULL)
 	{
-		HASHCTL		hash_ctl;
-
-		hash_ctl.keysize = NAMEDATALEN;
-		hash_ctl.entrysize = sizeof(plperl_query_entry);
-		interp_desc->query_hash = hash_create("PL/Perl queries",
-											  32,
-											  &hash_ctl,
-											  HASH_ELEM | HASH_STRINGS);
+		interp_desc->query_hash = hash_make_cxt(plperl_query_entry, query_name,
+												"PL/Perl queries", 32,
+												TopMemoryContext);
 	}
 
 	/*
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d19425b7a71..f3b469432aa 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3996,8 +3996,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 EState *simple_eval_estate,
 					 ResourceOwner simple_eval_resowner)
 {
-	HASHCTL		ctl;
-
 	/* this link will be restored at exit from plpgsql_call_handler */
 	func->cur_estate = estate;
 
@@ -4052,12 +4050,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	/* Create the session-wide cast-expression hash if we didn't already */
 	if (cast_expr_hash == NULL)
 	{
-		ctl.keysize = sizeof(plpgsql_CastHashKey);
-		ctl.entrysize = sizeof(plpgsql_CastExprHashEntry);
-		cast_expr_hash = hash_create("PLpgSQL cast expressions",
-									 16,	/* start small and extend */
-									 &ctl,
-									 HASH_ELEM | HASH_BLOBS);
+		cast_expr_hash = hash_make_cxt(plpgsql_CastExprHashEntry, key,
+									   "PLpgSQL cast expressions",
+									   16,	/* start small and extend */
+									   TopMemoryContext);
 	}
 
 	/* set up for use of appropriate simple-expression EState and cast hash */
@@ -4065,13 +4061,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	{
 		estate->simple_eval_estate = simple_eval_estate;
 		/* Private cast hash just lives in function's main context */
-		ctl.keysize = sizeof(plpgsql_CastHashKey);
-		ctl.entrysize = sizeof(plpgsql_CastHashEntry);
-		ctl.hcxt = CurrentMemoryContext;
-		estate->cast_hash = hash_create("PLpgSQL private cast cache",
-										16, /* start small and extend */
-										&ctl,
-										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+		estate->cast_hash = hash_make(plpgsql_CastHashEntry, key,
+									  "PLpgSQL private cast cache",
+									  16);	/* start small and extend */
 	}
 	else
 	{
@@ -4079,12 +4071,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 		/* Create the session-wide cast-info hash table if we didn't already */
 		if (shared_cast_hash == NULL)
 		{
-			ctl.keysize = sizeof(plpgsql_CastHashKey);
-			ctl.entrysize = sizeof(plpgsql_CastHashEntry);
-			shared_cast_hash = hash_create("PLpgSQL cast cache",
-										   16,	/* start small and extend */
-										   &ctl,
-										   HASH_ELEM | HASH_BLOBS);
+			shared_cast_hash = hash_make_cxt(plpgsql_CastHashEntry, key,
+											 "PLpgSQL cast cache",
+											 16,	/* start small and extend */
+											 TopMemoryContext);
 		}
 		estate->cast_hash = shared_cast_hash;
 	}
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 89931612c5b..b268b380cd5 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -16,6 +16,7 @@
 #include "plpy_subxactobject.h"
 #include "plpy_util.h"
 #include "utils/builtins.h"
+#include "utils/memutils.h"
 
 HTAB	   *PLy_spi_exceptions = NULL;
 
@@ -172,7 +173,6 @@ static void
 PLy_add_exceptions(PyObject *plpy)
 {
 	PyObject   *excmod;
-	HASHCTL		hash_ctl;
 
 	excmod = PyModule_Create(&PLy_exc_module);
 	if (excmod == NULL)
@@ -193,10 +193,9 @@ PLy_add_exceptions(PyObject *plpy)
 	PLy_exc_spi_error = PLy_create_exception("plpy.SPIError", NULL, NULL,
 											 "SPIError", plpy);
 
-	hash_ctl.keysize = sizeof(int);
-	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
-	PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256,
-									 &hash_ctl, HASH_ELEM | HASH_BLOBS);
+	PLy_spi_exceptions = hash_make_cxt(PLyExceptionEntry, sqlstate,
+									   "PL/Python SPI exceptions", 256,
+									   TopMemoryContext);
 
 	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
 }
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 655ab1d09ee..6317606e01e 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -29,12 +29,9 @@ static char *PLy_procedure_munge_source(const char *name, const char *src);
 void
 init_procedure_caches(void)
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = sizeof(PLyProcedureKey);
-	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
-	PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
-									  HASH_ELEM | HASH_BLOBS);
+	PLy_procedure_cache = hash_make_cxt(PLyProcedureEntry, key,
+										"PL/Python procedures", 32,
+										TopMemoryContext);
 }
 
 /*
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 73d660e88a6..a3ef429d926 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -408,7 +408,6 @@ void
 _PG_init(void)
 {
 	Tcl_NotifierProcs notifier;
-	HASHCTL		hash_ctl;
 
 	/* Be sure we do initialization only once (should be redundant now) */
 	if (pltcl_pm_init_done)
@@ -446,22 +445,16 @@ _PG_init(void)
 	/************************************************************
 	 * Create the hash table for working interpreters
 	 ************************************************************/
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(pltcl_interp_desc);
-	pltcl_interp_htab = hash_create("PL/Tcl interpreters",
-									8,
-									&hash_ctl,
-									HASH_ELEM | HASH_BLOBS);
+	pltcl_interp_htab = hash_make_cxt(pltcl_interp_desc, user_id,
+									  "PL/Tcl interpreters", 8,
+									  TopMemoryContext);
 
 	/************************************************************
 	 * Create the hash table for function lookup
 	 ************************************************************/
-	hash_ctl.keysize = sizeof(pltcl_proc_key);
-	hash_ctl.entrysize = sizeof(pltcl_proc_ptr);
-	pltcl_proc_htab = hash_create("PL/Tcl functions",
-								  100,
-								  &hash_ctl,
-								  HASH_ELEM | HASH_BLOBS);
+	pltcl_proc_htab = hash_make_cxt(pltcl_proc_ptr, proc_key,
+									"PL/Tcl functions", 100,
+									TopMemoryContext);
 
 	/************************************************************
 	 * Define PL/Tcl's custom GUCs
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 504c0235ffb..ce0edf98266 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -22,6 +22,7 @@
 #include "pgtz.h"
 #include "storage/fd.h"
 #include "utils/hsearch.h"
+#include "utils/memutils.h"

 /* Current session timezone (controlled by TimeZone GUC) */
@@ -201,15 +202,8 @@ static HTAB *timezone_cache = NULL;
 static bool
 init_timezone_hashtable(void)
 {
-	HASHCTL		hash_ctl;
-
-	hash_ctl.keysize = TZ_STRLEN_MAX + 1;
-	hash_ctl.entrysize = sizeof(pg_tz_cache);
-
-	timezone_cache = hash_create("Timezones",
-								 4,
-								 &hash_ctl,
-								 HASH_ELEM | HASH_STRINGS);
+	timezone_cache = hash_make_cxt(pg_tz_cache, tznameupper,
+								   "Timezones", 4, TopMemoryContext);
 	if (!timezone_cache)
 		return false;
 
-- 
2.52.0

From 72e3fd268cec5232d2dcf627de6857b99f85e3ff Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 3 Dec 2025 23:33:18 +0100
Subject: [PATCH v2 3/5] Inline functions that have now become trivial

---
 src/backend/commands/prepare.c      | 17 +++--------------
 src/backend/commands/sequence.c     | 16 +++-------------
 src/backend/utils/cache/funccache.c | 22 ++++------------------
 3 files changed, 10 insertions(+), 45 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 0002c6dc993..cc950ce2887 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -46,7 +46,6 @@
  */
 static HTAB *prepared_queries = NULL;
 
-static void InitQueryHashTable(void);
 static ParamListInfo EvaluateParams(ParseState *pstate,
 									PreparedStatement *pstmt, List *params,
 									EState *estate);
@@ -364,18 +363,6 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 	return paramLI;
 }
 
-
-/*
- * Initialize query hash table upon first use.
- */
-static void
-InitQueryHashTable(void)
-{
-	prepared_queries = hash_make_cxt(PreparedStatement, stmt_name,
-									 "Prepared Queries", 32,
-									 TopMemoryContext);
-}
-
 /*
  * Store all the data pertaining to a query in the hash table using
  * the specified key.  The passed CachedPlanSource should be "unsaved"
@@ -393,7 +380,9 @@ StorePreparedStatement(const char *stmt_name,
 
 	/* Initialize the hash table, if necessary */
 	if (!prepared_queries)
-		InitQueryHashTable();
+		prepared_queries = hash_make_cxt(PreparedStatement, stmt_name,
+										 "Prepared Queries", 32,
+										 TopMemoryContext);
 
 	/* Add entry to hash table */
 	entry = (PreparedStatement *) hash_search(prepared_queries,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5fbb3ce9030..aa037901613 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -89,7 +89,6 @@ static SeqTableData *last_used_seq = NULL;
 static void fill_seq_with_data(Relation rel, HeapTuple tuple);
 static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum);
 static Relation lock_and_open_sequence(SeqTable seq);
-static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
 static Form_pg_sequence_data read_seq_tuple(Relation rel,
 											Buffer *buf, HeapTuple seqdatatuple);
@@ -1107,17 +1106,6 @@ lock_and_open_sequence(SeqTable seq)
 	return sequence_open(seq->relid, NoLock);
 }
 
-/*
- * Creates the hash table for storing sequence data
- */
-static void
-create_seq_hashtable(void)
-{
-	seqhashtab = hash_make_cxt(SeqTableData, relid,
-							   "Sequence values", 16,
-							   TopMemoryContext);
-}
-
 /*
  * Given a relation OID, open and lock the sequence.  p_elm and p_rel are
  * output parameters.
@@ -1131,7 +1119,9 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 
 	/* Find or create a hash table entry for this sequence */
 	if (seqhashtab == NULL)
-		create_seq_hashtable();
+		seqhashtab = hash_make_cxt(SeqTableData, relid,
+								   "Sequence values", 16,
+								   TopMemoryContext);
 
 	elm = (SeqTable) hash_search(seqhashtab, &relid, HASH_ENTER, &found);
 
diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c
index 34438a9f93b..613fcfdc9f5 100644
--- a/src/backend/utils/cache/funccache.c
+++ b/src/backend/utils/cache/funccache.c
@@ -50,23 +50,6 @@ static uint32 cfunc_hash(const void *key, Size keysize);
 static int	cfunc_match(const void *key1, const void *key2, Size keysize);

-/*
- * Initialize the hash table on first use.
- *
- * The hash table will be in TopMemoryContext regardless of caller's context.
- */
-static void
-cfunc_hashtable_init(void)
-{
-	/* don't allow double-initialization */
-	Assert(cfunc_hashtable == NULL);
-
-	cfunc_hashtable = hash_make_fn_cxt(CachedFunctionHashEntry, key,
-									   "Cached function hash", FUNCS_PER_USER,
-									   cfunc_hash, cfunc_match,
-									   TopMemoryContext);
-}
-
 /*
  * cfunc_hash: hash function for cfunc hash table
  *
@@ -165,7 +148,10 @@ cfunc_hashtable_insert(CachedFunction *function,
 	bool		found;
 
 	if (cfunc_hashtable == NULL)
-		cfunc_hashtable_init();
+		cfunc_hashtable = hash_make_fn_cxt(CachedFunctionHashEntry, key,
+										   "Cached function hash", FUNCS_PER_USER,
+										   cfunc_hash, cfunc_match,
+										   TopMemoryContext);
 
 	hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable,
 													 func_key,
-- 
2.52.0

From e5eae3786fa0c846a3e01f2758e7dfa481a27410 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:00 +0100
Subject: [PATCH v2 4/5] Add foreach_hash macro

For lists we've had a new foreach style macros since 14dd0f27d7. This
adds a similar macro for hash tables. This new foreach_hash macro makes
iterating over the items in an HTAB as simple as iterating over the
items in a List. The only additional thing to keep in mind is that when
exiting the loop early you need to call foreach_hash_term.
---
 src/include/utils/hsearch.h | 46 +++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 36b31be7ad6..b3a361d78ad 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -277,6 +277,52 @@ typedef struct
 	uint32		hashvalue;		/* hashvalue to start seqscan over hash */
 } HASH_SEQ_STATUS;
 
+/*
+ * foreach_hash - iterate over all entries in a hash table
+ *
+ * This macro simplifies hash table iteration by combining hash_seq_init
+ * and hash_seq_search into a single for-loop construct.
+ *
+ * Usage:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * This replaces the more verbose pattern:
+ *   HASH_SEQ_STATUS status;
+ *   MyEntry *entry;
+ *   hash_seq_init(&status, my_hashtable);
+ *   while ((entry = (MyEntry *) hash_seq_search(&status)) != NULL)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * For early termination, use foreach_hash_term() before break:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       if (found_it)
+ *       {
+ *           foreach_hash_term(entry);
+ *           break;
+ *       }
+ *   }
+ */
+#define foreach_hash(type, var, htab) \
+	for (type *var = 0, *var##__outerloop = (type *) 1; \
+		 var##__outerloop; \
+		 var##__outerloop = 0) \
+		for (HASH_SEQ_STATUS var##__status = (hash_seq_init(&var##__status, (htab)), var##__status); \
+			 (var = (type *) hash_seq_search(&var##__status)) != NULL; \
+			)
+
+/*
+ * foreach_hash_term - terminate a foreach_hash loop early
+ *
+ * Call this before 'break' to properly clean up the hash scan.
+ */
+#define foreach_hash_term(var) hash_seq_term(&var##__status)
+
 /*
  * prototypes for functions in dynahash.c
  */
-- 
2.52.0

From a2ed4a5bddc8c87c14bb1c15f28e5327647f6da0 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:09 +0100
Subject: [PATCH v2 5/5] Use foreach_hash macro throughout the codebase

This starts using the new foreach_hash macro throughout the codebase.
This makes code easier to read, but obviously does introduce
backpatching problems. We can choose not to do this refactor to avoid
that. Or we could instead choose to do the refactor and then backpatch
these new macros so they can be used in backpatched code.

At the very least we should choose a few places where we use the new
macros to make sure they have coverage.
---
 contrib/dblink/dblink.c                       |  5 +-
 .../pg_stat_statements/pg_stat_statements.c   | 39 ++++-------
 contrib/pg_trgm/trgm_regexp.c                 | 18 ++---
 contrib/postgres_fdw/connection.c             | 26 ++-----
 contrib/postgres_fdw/shippable.c              |  6 +-
 src/backend/access/heap/rewriteheap.c         | 18 +----
 src/backend/access/transam/xlogutils.c        | 20 +-----
 src/backend/catalog/pg_enum.c                 | 16 ++---
 src/backend/catalog/storage.c                 | 18 ++---
 src/backend/commands/prepare.c                | 12 +---
 src/backend/commands/tablecmds.c              |  7 +-
 src/backend/optimizer/util/predtest.c         |  7 +-
 src/backend/parser/parse_oper.c               |  7 +-
 src/backend/partitioning/partdesc.c           |  6 +-
 src/backend/postmaster/autovacuum.c           |  7 +-
 src/backend/replication/logical/relation.c    | 37 ++--------
 .../replication/logical/reorderbuffer.c       | 12 +---
 src/backend/replication/pgoutput/pgoutput.c   | 24 ++-----
 src/backend/storage/buffer/bufmgr.c           | 10 +--
 src/backend/storage/ipc/shmem.c               | 12 +---
 src/backend/storage/ipc/standby.c             | 12 +---
 src/backend/storage/lmgr/lock.c               | 67 ++++---------------
 src/backend/storage/lmgr/lwlock.c             |  7 +-
 src/backend/storage/lmgr/predicate.c          | 18 +----
 src/backend/storage/smgr/smgr.c               |  7 +-
 src/backend/storage/sync/sync.c               | 13 +---
 src/backend/tsearch/ts_typanalyze.c           | 11 +--
 src/backend/utils/activity/wait_event.c       |  6 +-
 src/backend/utils/adt/array_typanalyze.c      | 17 ++---
 src/backend/utils/cache/relcache.c            | 42 ++++--------
 src/backend/utils/cache/relfilenumbermap.c    |  6 +-
 src/backend/utils/cache/spccache.c            |  6 +-
 src/backend/utils/cache/ts_cache.c            |  5 +-
 src/backend/utils/misc/guc.c                  | 30 ++-------
 src/backend/utils/mmgr/portalmem.c            | 67 +++----------------
 35 files changed, 131 insertions(+), 490 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 2951c39d69f..d621465bd3b 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -1275,14 +1275,11 @@ PG_FUNCTION_INFO_V1(dblink_get_connections);
 Datum
 dblink_get_connections(PG_FUNCTION_ARGS)
 {
-	HASH_SEQ_STATUS status;
-	remoteConnHashEnt *hentry;
 	ArrayBuildState *astate = NULL;
 
 	if (remoteConnHash)
 	{
-		hash_seq_init(&status, remoteConnHash);
-		while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL)
+		foreach_hash(remoteConnHashEnt, hentry, remoteConnHash)
 		{
 			/* ignore it if it's not an open connection */
 			if (hentry->rconn.conn == NULL)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 2c55b78e9ba..a2e13f0a5d8 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -741,9 +741,7 @@ pgss_shmem_shutdown(int code, Datum arg)
 	FILE	   *file;
 	char	   *qbuffer = NULL;
 	Size		qbuffer_size = 0;
-	HASH_SEQ_STATUS hash_seq;
 	int32		num_entries;
-	pgssEntry  *entry;
 
 	/* Don't try to dump during a crash. */
 	if (code)
@@ -777,8 +775,7 @@ pgss_shmem_shutdown(int code, Datum arg)
 	 * When serializing to disk, we store query texts immediately after their
 	 * entry data.  Any orphaned query texts are thereby excluded.
 	 */
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		int			len = entry->query_len;
 		char	   *qstr = qtext_fetch(entry->query_offset, len,
@@ -790,8 +787,8 @@ pgss_shmem_shutdown(int code, Datum arg)
 		if (fwrite(entry, sizeof(pgssEntry), 1, file) != 1 ||
 			fwrite(qstr, 1, len + 1, file) != len + 1)
 		{
-			/* note: we assume hash_seq_term won't change errno */
-			hash_seq_term(&hash_seq);
+			/* note: we assume foreach_hash_term won't change errno */
+			foreach_hash_term(entry);
 			goto error;
 		}
 	}
@@ -1695,8 +1692,6 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 	Size		qbuffer_size = 0;
 	Size		extent = 0;
 	int			gc_count = 0;
-	HASH_SEQ_STATUS hash_seq;
-	pgssEntry  *entry;
 
 	/*
 	 * Superusers or roles with the privileges of pg_read_all_stats members
@@ -1825,8 +1820,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		}
 	}
 
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		Datum		values[PG_STAT_STATEMENTS_COLS];
 		bool		nulls[PG_STAT_STATEMENTS_COLS];
@@ -2170,9 +2164,7 @@ entry_cmp(const void *lhs, const void *rhs)
 static void
 entry_dealloc(void)
 {
-	HASH_SEQ_STATUS hash_seq;
 	pgssEntry **entries;
-	pgssEntry  *entry;
 	int			nvictims;
 	int			i;
 	Size		tottextlen;
@@ -2196,8 +2188,7 @@ entry_dealloc(void)
 	tottextlen = 0;
 	nvalidtexts = 0;
 
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		entries[i++] = entry;
 		/* "Sticky" entries get a different usage decay rate. */
@@ -2509,8 +2500,6 @@ gc_qtexts(void)
 	char	   *qbuffer;
 	Size		qbuffer_size;
 	FILE	   *qfile = NULL;
-	HASH_SEQ_STATUS hash_seq;
-	pgssEntry  *entry;
 	Size		extent;
 	int			nentries;
 
@@ -2552,8 +2541,7 @@ gc_qtexts(void)
 	extent = 0;
 	nentries = 0;
 
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		int			query_len = entry->query_len;
 		char	   *qry = qtext_fetch(entry->query_offset,
@@ -2576,7 +2564,7 @@ gc_qtexts(void)
 					(errcode_for_file_access(),
 					 errmsg("could not write file \"%s\": %m",
 							PGSS_TEXT_FILE)));
-			hash_seq_term(&hash_seq);
+			foreach_hash_term(entry);
 			goto gc_fail;
 		}
 
@@ -2643,8 +2631,7 @@ gc_fail:
 	 * Since the contents of the external file are now uncertain, mark all
 	 * hashtable entries as having invalid texts.
 	 */
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		entry->query_offset = 0;
 		entry->query_len = -1;
@@ -2708,8 +2695,6 @@ if (e) { \
 static TimestampTz
 entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 {
-	HASH_SEQ_STATUS hash_seq;
-	pgssEntry  *entry;
 	FILE	   *qfile;
 	int64		num_entries;
 	int64		num_remove = 0;
@@ -2729,6 +2714,8 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 	if (userid != 0 && dbid != 0 && queryid != INT64CONST(0))
 	{
 		/* If all the parameters are available, use the fast path. */
+		pgssEntry  *entry;
+
 		memset(&key, 0, sizeof(pgssHashKey));
 		key.userid = userid;
 		key.dbid = dbid;
@@ -2752,8 +2739,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 	else if (userid != 0 || dbid != 0 || queryid != INT64CONST(0))
 	{
 		/* Reset entries corresponding to valid parameters. */
-		hash_seq_init(&hash_seq, pgss_hash);
-		while ((entry = hash_seq_search(&hash_seq)) != NULL)
+		foreach_hash(pgssEntry, entry, pgss_hash)
 		{
 			if ((!userid || entry->key.userid == userid) &&
 				(!dbid || entry->key.dbid == dbid) &&
@@ -2766,8 +2752,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 	else
 	{
 		/* Reset all entries. */
-		hash_seq_init(&hash_seq, pgss_hash);
-		while ((entry = hash_seq_search(&hash_seq)) != NULL)
+		foreach_hash(pgssEntry, entry, pgss_hash)
 		{
 			SINGLE_ENTRY_RESET(entry);
 		}
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index ced4d333b9a..c032d8b0ac8 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -1450,10 +1450,8 @@ prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2)
 static bool
 selectColorTrigrams(TrgmNFA *trgmNFA)
 {
-	HASH_SEQ_STATUS scan_status;
 	int			arcsCount = trgmNFA->arcsCount,
 				i;
-	TrgmState  *state;
 	ColorTrgmInfo *colorTrgms;
 	int64		totalTrgmCount;
 	float4		totalTrgmPenalty;
@@ -1464,8 +1462,7 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
 	trgmNFA->colorTrgms = colorTrgms;
 
 	i = 0;
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		ListCell   *cell;
 
@@ -1927,8 +1924,6 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 	int			snumber = 2,
 				arcIndex,
 				arcsCount;
-	HASH_SEQ_STATUS scan_status;
-	TrgmState  *state;
 	TrgmPackArcInfo *arcs;
 	TrgmPackedArc *packedArcs;
 	TrgmPackedGraph *result;
@@ -1936,8 +1931,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 				j;
 
 	/* Enumerate surviving states, giving init and fin reserved numbers */
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		while (state->parent)
 			state = state->parent;
@@ -1960,8 +1954,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 	arcs = (TrgmPackArcInfo *)
 		palloc(sizeof(TrgmPackArcInfo) * trgmNFA->arcsCount);
 	arcIndex = 0;
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		TrgmState  *source = state;
 		ListCell   *cell;
@@ -2204,16 +2197,13 @@ static void
 printTrgmNFA(TrgmNFA *trgmNFA)
 {
 	StringInfoData buf;
-	HASH_SEQ_STATUS scan_status;
-	TrgmState  *state;
 	TrgmState  *initstate = NULL;
 
 	initStringInfo(&buf);
 
 	appendStringInfoString(&buf, "\ndigraph transformedNFA {\n");
 
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		ListCell   *cell;
 
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 042a7fb3ac1..42863b02909 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1044,8 +1044,6 @@ pgfdw_report_internal(int elevel, PGresult *res, PGconn *conn,
 static void
 pgfdw_xact_callback(XactEvent event, void *arg)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 	List	   *pending_entries = NIL;
 	List	   *cancel_requested = NIL;
 
@@ -1057,8 +1055,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 	 * Scan all connection cache entries to find open remote transactions, and
 	 * close them.
 	 */
-	hash_seq_init(&scan, ConnectionHash);
-	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+	foreach_hash(ConnCacheEntry, entry, ConnectionHash)
 	{
 		PGresult   *res;
 
@@ -1195,8 +1192,6 @@ static void
 pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 					   SubTransactionId parentSubid, void *arg)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 	int			curlevel;
 	List	   *pending_entries = NIL;
 	List	   *cancel_requested = NIL;
@@ -1215,8 +1210,7 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 	 * of the current level, and close them.
 	 */
 	curlevel = GetCurrentTransactionNestLevel();
-	hash_seq_init(&scan, ConnectionHash);
-	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+	foreach_hash(ConnCacheEntry, entry, ConnectionHash)
 	{
 		char		sql[100];
 
@@ -1307,14 +1301,10 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 static void
 pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
-
 	Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
 
 	/* ConnectionHash must exist already, if we're registered */
-	hash_seq_init(&scan, ConnectionHash);
-	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+	foreach_hash(ConnCacheEntry, entry, ConnectionHash)
 	{
 		/* Ignore invalid entries */
 		if (entry->conn == NULL)
@@ -2165,8 +2155,6 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 									  enum pgfdwVersion api_version)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 
 	InitMaterializedSRF(fcinfo, 0);
 
@@ -2189,8 +2177,7 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 			elog(ERROR, "incorrect number of output arguments");
 	}
 
-	hash_seq_init(&scan, ConnectionHash);
-	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+	foreach_hash(ConnCacheEntry, entry, ConnectionHash)
 	{
 		ForeignServer *server;
 		Datum		values[POSTGRES_FDW_GET_CONNECTIONS_COLS] = {0};
@@ -2392,8 +2379,6 @@ postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
 static bool
 disconnect_cached_connections(Oid serverid)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 	bool		all = !OidIsValid(serverid);
 	bool		result = false;
 
@@ -2404,8 +2389,7 @@ disconnect_cached_connections(Oid serverid)
 	if (!ConnectionHash)
 		return false;
 
-	hash_seq_init(&scan, ConnectionHash);
-	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+	foreach_hash(ConnCacheEntry, entry, ConnectionHash)
 	{
 		/* Ignore cache entry if no open connection right now. */
 		if (!entry->conn)
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
index 66d2d0b9ff4..bc5dc90e541 100644
--- a/contrib/postgres_fdw/shippable.c
+++ b/contrib/postgres_fdw/shippable.c
@@ -65,17 +65,13 @@ typedef struct
 static void
 InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	ShippableCacheEntry *entry;
-
 	/*
 	 * In principle we could flush only cache entries relating to the
 	 * pg_foreign_server entry being outdated; but that would be more
 	 * complicated, and it's probably not worth the trouble.  So for now, just
 	 * flush all entries.
 	 */
-	hash_seq_init(&status, ShippableCacheHash);
-	while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(ShippableCacheEntry, entry, ShippableCacheHash)
 	{
 		if (hash_search(ShippableCacheHash,
 						&entry->key,
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index dc2c46742fd..32433fce4a4 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -290,16 +290,11 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 void
 end_heap_rewrite(RewriteState state)
 {
-	HASH_SEQ_STATUS seq_status;
-	UnresolvedTup unresolved;
-
 	/*
 	 * Write any remaining tuples in the UnresolvedTups table. If we have any
 	 * left, they should in fact be dead, but let's err on the safe side.
 	 */
-	hash_seq_init(&seq_status, state->rs_unresolved_tups);
-
-	while ((unresolved = hash_seq_search(&seq_status)) != NULL)
+	foreach_hash(UnresolvedTupData, unresolved, state->rs_unresolved_tups)
 	{
 		ItemPointerSetInvalid(&unresolved->tuple->t_data->t_ctid);
 		raw_heap_insert(state, unresolved->tuple);
@@ -794,8 +789,6 @@ logical_begin_heap_rewrite(RewriteState state)
 static void
 logical_heap_rewrite_flush_mappings(RewriteState state)
 {
-	HASH_SEQ_STATUS seq_status;
-	RewriteMappingFile *src;
 	dlist_mutable_iter iter;
 
 	Assert(state->rs_logical_rewrite);
@@ -807,8 +800,7 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
 	elog(DEBUG1, "flushing %u logical rewrite mapping entries",
 		 state->rs_num_rewrite_mappings);
 
-	hash_seq_init(&seq_status, state->rs_logical_mappings);
-	while ((src = (RewriteMappingFile *) hash_seq_search(&seq_status)) != NULL)
+	foreach_hash(RewriteMappingFile, src, state->rs_logical_mappings)
 	{
 		char	   *waldata;
 		char	   *waldata_start;
@@ -892,9 +884,6 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
 static void
 logical_end_heap_rewrite(RewriteState state)
 {
-	HASH_SEQ_STATUS seq_status;
-	RewriteMappingFile *src;
-
 	/* done, no logical rewrite in progress */
 	if (!state->rs_logical_rewrite)
 		return;
@@ -904,8 +893,7 @@ logical_end_heap_rewrite(RewriteState state)
 		logical_heap_rewrite_flush_mappings(state);
 
 	/* Iterate over all mappings we have written and fsync the files. */
-	hash_seq_init(&seq_status, state->rs_logical_mappings);
-	while ((src = (RewriteMappingFile *) hash_seq_search(&seq_status)) != NULL)
+	foreach_hash(RewriteMappingFile, src, state->rs_logical_mappings)
 	{
 		if (FileSync(src->vfd, WAIT_EVENT_LOGICAL_REWRITE_SYNC) != 0)
 			ereport(data_sync_elevel(ERROR),
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 9e24759f5cc..ef13f04e08d 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -160,15 +160,10 @@ static void
 forget_invalid_pages(RelFileLocator locator, ForkNumber forkno,
 					 BlockNumber minblkno)
 {
-	HASH_SEQ_STATUS status;
-	xl_invalid_page *hentry;
-
 	if (invalid_page_tab == NULL)
 		return;					/* nothing to do */
 
-	hash_seq_init(&status, invalid_page_tab);
-
-	while ((hentry = (xl_invalid_page *) hash_seq_search(&status)) != NULL)
+	foreach_hash(xl_invalid_page, hentry, invalid_page_tab)
 	{
 		if (RelFileLocatorEquals(hentry->key.locator, locator) &&
 			hentry->key.forkno == forkno &&
@@ -190,15 +185,10 @@ forget_invalid_pages(RelFileLocator locator, ForkNumber forkno,
 static void
 forget_invalid_pages_db(Oid dbid)
 {
-	HASH_SEQ_STATUS status;
-	xl_invalid_page *hentry;
-
 	if (invalid_page_tab == NULL)
 		return;					/* nothing to do */
 
-	hash_seq_init(&status, invalid_page_tab);
-
-	while ((hentry = (xl_invalid_page *) hash_seq_search(&status)) != NULL)
+	foreach_hash(xl_invalid_page, hentry, invalid_page_tab)
 	{
 		if (hentry->key.locator.dbOid == dbid)
 		{
@@ -228,20 +218,16 @@ XLogHaveInvalidPages(void)
 void
 XLogCheckInvalidPages(void)
 {
-	HASH_SEQ_STATUS status;
-	xl_invalid_page *hentry;
 	bool		foundone = false;
 
 	if (invalid_page_tab == NULL)
 		return;					/* nothing to do */
 
-	hash_seq_init(&status, invalid_page_tab);
-
 	/*
 	 * Our strategy is to emit WARNING messages for all remaining entries and
 	 * only PANIC after we've dumped all the available info.
 	 */
-	while ((hentry = (xl_invalid_page *) hash_seq_search(&status)) != NULL)
+	foreach_hash(xl_invalid_page, hentry, invalid_page_tab)
 	{
 		report_invalid_page(WARNING, hentry->key.locator, hentry->key.forkno,
 							hentry->key.blkno, hentry->present);
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index 4787a61c7d3..fca136b68ad 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -838,12 +838,10 @@ SerializeUncommittedEnums(void *space, Size size)
 	/* Write out all the OIDs from the types hash table, if there is one. */
 	if (uncommitted_enum_types)
 	{
-		HASH_SEQ_STATUS status;
-		Oid		   *value;
-
-		hash_seq_init(&status, uncommitted_enum_types);
-		while ((value = (Oid *) hash_seq_search(&status)))
+		foreach_hash(Oid, value, uncommitted_enum_types)
+		{
 			*serialized++ = *value;
+		}
 	}
 
 	/* Write out the terminator. */
@@ -852,12 +850,8 @@ SerializeUncommittedEnums(void *space, Size size)
 	/* Write out all the OIDs from the values hash table, if there is one. */
 	if (uncommitted_enum_values)
 	{
-		HASH_SEQ_STATUS status;
-		Oid		   *value;
-
-		hash_seq_init(&status, uncommitted_enum_values);
-		while ((value = (Oid *) hash_seq_search(&status)))
-			*serialized++ = *value;
+		foreach_hash(Oid, value, uncommitted_enum_values)
+			* serialized++ = *value;
 	}
 
 	/* Write out the terminator. */
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 1d13b4a1f39..dc44cae9e3e 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -594,10 +594,7 @@ void
 SerializePendingSyncs(Size maxSize, char *startAddress)
 {
 	HTAB	   *tmphash;
-	HASH_SEQ_STATUS scan;
-	PendingRelSync *sync;
 	PendingRelDelete *delete;
-	RelFileLocator *src;
 	RelFileLocator *dest = (RelFileLocator *) startAddress;
 
 	if (!pendingSyncHash)
@@ -608,9 +605,10 @@ SerializePendingSyncs(Size maxSize, char *startAddress)
 						   hash_get_num_entries(pendingSyncHash));
 
 	/* collect all rlocator from pending syncs */
-	hash_seq_init(&scan, pendingSyncHash);
-	while ((sync = (PendingRelSync *) hash_seq_search(&scan)))
+	foreach_hash(PendingRelSync, sync, pendingSyncHash)
+	{
 		(void) hash_search(tmphash, &sync->rlocator, HASH_ENTER, NULL);
+	}
 
 	/* remove deleted rnodes */
 	for (delete = pendingDeletes; delete != NULL; delete = delete->next)
@@ -618,9 +616,10 @@ SerializePendingSyncs(Size maxSize, char *startAddress)
 			(void) hash_search(tmphash, &delete->rlocator,
 							   HASH_REMOVE, NULL);
 
-	hash_seq_init(&scan, tmphash);
-	while ((src = (RelFileLocator *) hash_seq_search(&scan)))
+	foreach_hash(RelFileLocator, src, tmphash)
+	{
 		*dest++ = *src;
+	}
 
 	hash_destroy(tmphash);
 
@@ -733,8 +732,6 @@ smgrDoPendingSyncs(bool isCommit, bool isParallelWorker)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
-	HASH_SEQ_STATUS scan;
-	PendingRelSync *pendingsync;
 
 	Assert(GetCurrentTransactionNestLevel() == 1);
 
@@ -763,8 +760,7 @@ smgrDoPendingSyncs(bool isCommit, bool isParallelWorker)
 			(void) hash_search(pendingSyncHash, &pending->rlocator,
 							   HASH_REMOVE, NULL);
 
-	hash_seq_init(&scan, pendingSyncHash);
-	while ((pendingsync = (PendingRelSync *) hash_seq_search(&scan)))
+	foreach_hash(PendingRelSync, pendingsync, pendingSyncHash)
 	{
 		ForkNumber	fork;
 		BlockNumber nblocks[MAX_FORKNUM + 1];
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index cc950ce2887..7d4e4bd862c 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -522,16 +522,12 @@ DropPreparedStatement(const char *stmt_name, bool showError)
 void
 DropAllPreparedStatements(void)
 {
-	HASH_SEQ_STATUS seq;
-	PreparedStatement *entry;
-
 	/* nothing cached */
 	if (!prepared_queries)
 		return;
 
 	/* walk over cache */
-	hash_seq_init(&seq, prepared_queries);
-	while ((entry = hash_seq_search(&seq)) != NULL)
+	foreach_hash(PreparedStatement, entry, prepared_queries)
 	{
 		/* Release the plancache entry */
 		DropCachedPlan(entry->plansource);
@@ -678,11 +674,7 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
 	/* hash table might be uninitialized */
 	if (prepared_queries)
 	{
-		HASH_SEQ_STATUS hash_seq;
-		PreparedStatement *prep_stmt;
-
-		hash_seq_init(&hash_seq, prepared_queries);
-		while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL)
+		foreach_hash(PreparedStatement, prep_stmt, prepared_queries)
 		{
 			TupleDesc	result_desc;
 			Datum		values[8];
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7f0fb263772..9a55662af55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2236,14 +2236,9 @@ ExecuteTruncateGuts(List *explicit_rels,
 	/* Now go through the hash table, and truncate foreign tables */
 	if (ft_htab)
 	{
-		ForeignTruncateInfo *ft_info;
-		HASH_SEQ_STATUS seq;
-
-		hash_seq_init(&seq, ft_htab);
-
 		PG_TRY();
 		{
-			while ((ft_info = hash_seq_search(&seq)) != NULL)
+			foreach_hash(ForeignTruncateInfo, ft_info, ft_htab)
 			{
 				FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
 
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 43291f128fd..fa52f7a776d 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -2342,15 +2342,10 @@ get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it)
 static void
 InvalidateOprProofCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	OprProofCacheEntry *hentry;
-
 	Assert(OprProofCacheHash != NULL);
 
 	/* Currently we just reset all entries; hard to be smarter ... */
-	hash_seq_init(&status, OprProofCacheHash);
-
-	while ((hentry = (OprProofCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(OprProofCacheEntry, hentry, OprProofCacheHash)
 	{
 		hentry->have_implic = false;
 		hentry->have_refute = false;
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index f9588865cfc..91382962d94 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -1076,15 +1076,10 @@ make_oper_cache_entry(OprCacheKey *key, Oid opr_oid)
 static void
 InvalidateOprCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	OprCacheEntry *hentry;
-
 	Assert(OprCacheHash != NULL);
 
 	/* Currently we just flush all entries; hard to be smarter ... */
-	hash_seq_init(&status, OprCacheHash);
-
-	while ((hentry = (OprCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(OprCacheEntry, hentry, OprCacheHash)
 	{
 		if (hash_search(OprCacheHash,
 						&hentry->key,
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index b3fbd781494..908b0e39b8c 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -478,11 +478,7 @@ PartitionDirectoryLookup(PartitionDirectory pdir, Relation rel)
 void
 DestroyPartitionDirectory(PartitionDirectory pdir)
 {
-	HASH_SEQ_STATUS status;
-	PartitionDirectoryEntry *pde;
-
-	hash_seq_init(&status, pdir->pdir_hash);
-	while ((pde = hash_seq_search(&status)) != NULL)
+	foreach_hash(PartitionDirectoryEntry, pde, pdir->pdir_hash)
 		RelationDecrementReferenceCount(pde->rel);
 }
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f1b6b1a24a9..b0df7fba690 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1021,8 +1021,6 @@ rebuild_database_list(Oid newdb)
 		TimestampTz current_time;
 		int			millis_increment;
 		avl_dbase  *dbary;
-		avl_dbase  *db;
-		HASH_SEQ_STATUS seq;
 		int			i;
 
 		/* put all the hash elements into an array */
@@ -1033,8 +1031,7 @@ rebuild_database_list(Oid newdb)
 #endif
 
 		i = 0;
-		hash_seq_init(&seq, dbhash);
-		while ((db = hash_seq_search(&seq)) != NULL)
+		foreach_hash(avl_dbase, db, dbhash)
 			memcpy(&(dbary[i++]), db, sizeof(avl_dbase));
 
 		/* sort the array */
@@ -1059,7 +1056,7 @@ rebuild_database_list(Oid newdb)
 		 */
 		for (i = 0; i < nelems; i++)
 		{
-			db = &(dbary[i]);
+			avl_dbase  *db = &(dbary[i]);
 
 			current_time = TimestampTzPlusMilliseconds(current_time,
 													   millis_increment);
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index c4af85d74f1..57f5872984b 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -63,25 +63,19 @@ static Oid	FindLogicalRepLocalIndex(Relation localrel, LogicalRepRelation *remot
 static void
 logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
 {
-	LogicalRepRelMapEntry *entry;
-
 	/* Just to be sure. */
 	if (LogicalRepRelMap == NULL)
 		return;
 
 	if (reloid != InvalidOid)
 	{
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, LogicalRepRelMap);
-
 		/* TODO, use inverse lookup hashtable? */
-		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LogicalRepRelMapEntry, entry, LogicalRepRelMap)
 		{
 			if (entry->localreloid == reloid)
 			{
 				entry->localrelvalid = false;
-				hash_seq_term(&status);
+				foreach_hash_term(entry);
 				break;
 			}
 		}
@@ -89,11 +83,7 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
 	else
 	{
 		/* invalidate all cache entries */
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, LogicalRepRelMap);
-
-		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LogicalRepRelMapEntry, entry, LogicalRepRelMap)
 			entry->localrelvalid = false;
 	}
 }
@@ -530,25 +520,19 @@ logicalrep_rel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode)
 static void
 logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
 {
-	LogicalRepPartMapEntry *entry;
-
 	/* Just to be sure. */
 	if (LogicalRepPartMap == NULL)
 		return;
 
 	if (reloid != InvalidOid)
 	{
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, LogicalRepPartMap);
-
 		/* TODO, use inverse lookup hashtable? */
-		while ((entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LogicalRepPartMapEntry, entry, LogicalRepPartMap)
 		{
 			if (entry->relmapentry.localreloid == reloid)
 			{
 				entry->relmapentry.localrelvalid = false;
-				hash_seq_term(&status);
+				foreach_hash_term(entry);
 				break;
 			}
 		}
@@ -556,11 +540,7 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
 	else
 	{
 		/* invalidate all cache entries */
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, LogicalRepPartMap);
-
-		while ((entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LogicalRepPartMapEntry, entry, LogicalRepPartMap)
 			entry->relmapentry.localrelvalid = false;
 	}
 }
@@ -578,15 +558,12 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
 void
 logicalrep_partmap_reset_relmap(LogicalRepRelation *remoterel)
 {
-	HASH_SEQ_STATUS status;
-	LogicalRepPartMapEntry *part_entry;
 	LogicalRepRelMapEntry *entry;
 
 	if (LogicalRepPartMap == NULL)
 		return;
 
-	hash_seq_init(&status, LogicalRepPartMap);
-	while ((part_entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LogicalRepPartMapEntry, part_entry, LogicalRepPartMap)
 	{
 		entry = &part_entry->relmapentry;
 
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e09516d3d8b..562fd743d94 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -5250,15 +5250,11 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 static void
 ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASH_SEQ_STATUS hstat;
-	ReorderBufferToastEnt *ent;
-
 	if (txn->toast_hash == NULL)
 		return;
 
 	/* sequentially walk over the hash and free everything */
-	hash_seq_init(&hstat, txn->toast_hash);
-	while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
+	foreach_hash(ReorderBufferToastEnt, ent, txn->toast_hash)
 	{
 		dlist_mutable_iter it;
 
@@ -5321,11 +5317,7 @@ typedef struct RewriteMappingFile
 static void
 DisplayMapping(HTAB *tuplecid_data)
 {
-	HASH_SEQ_STATUS hstat;
-	ReorderBufferTupleCidEnt *ent;
-
-	hash_seq_init(&hstat, tuplecid_data);
-	while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL)
+	foreach_hash(ReorderBufferTupleCidEnt, ent, tuplecid_data)
 	{
 		elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u",
 			 ent->key.rlocator.dbOid,
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 6dea24ff0a6..3750a880aed 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -2339,13 +2339,9 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 static void
 cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
 {
-	HASH_SEQ_STATUS hash_seq;
-	RelationSyncEntry *entry;
-
 	Assert(RelationSyncCache != NULL);
 
-	hash_seq_init(&hash_seq, RelationSyncCache);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 	{
 		/*
 		 * We can set the schema_sent flag for an entry that has committed xid
@@ -2374,8 +2370,6 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
 static void
 rel_sync_cache_relation_cb(Datum arg, Oid relid)
 {
-	RelationSyncEntry *entry;
-
 	/*
 	 * We can get here if the plugin was used in SQL interface as the
 	 * RelationSyncCache is destroyed when the decoding finishes, but there is
@@ -2398,18 +2392,16 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
 		 * Getting invalidations for relations that aren't in the table is
 		 * entirely normal.  So we don't care if it's found or not.
 		 */
-		entry = (RelationSyncEntry *) hash_search(RelationSyncCache, &relid,
-												  HASH_FIND, NULL);
+		RelationSyncEntry *entry = hash_search(RelationSyncCache, &relid,
+											   HASH_FIND, NULL);
+
 		if (entry != NULL)
 			entry->replicate_valid = false;
 	}
 	else
 	{
 		/* Whole cache must be flushed. */
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, RelationSyncCache);
-		while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 		{
 			entry->replicate_valid = false;
 		}
@@ -2424,9 +2416,6 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
 static void
 rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	RelationSyncEntry *entry;
-
 	/*
 	 * We can get here if the plugin was used in SQL interface as the
 	 * RelationSyncCache is destroyed when the decoding finishes, but there is
@@ -2439,8 +2428,7 @@ rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
 	 * We have no easy way to identify which cache entries this invalidation
 	 * event might have affected, so just mark them all invalid.
 	 */
-	hash_seq_init(&status, RelationSyncCache);
-	while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 	{
 		entry->replicate_valid = false;
 	}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f5932843cc..b96d89abe95 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -4057,14 +4057,13 @@ CheckForBufferLeaks(void)
 {
 #ifdef USE_ASSERT_CHECKING
 	int			RefCountErrors = 0;
-	PrivateRefCountEntry *res;
 	int			i;
 	char	   *s;
 
 	/* check the array */
 	for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
 	{
-		res = &PrivateRefCountArray[i];
+		PrivateRefCountEntry *res = &PrivateRefCountArray[i];
 
 		if (res->buffer != InvalidBuffer)
 		{
@@ -4079,12 +4078,9 @@ CheckForBufferLeaks(void)
 	/* if necessary search the hash */
 	if (PrivateRefCountOverflowed)
 	{
-		HASH_SEQ_STATUS hstat;
-
-		hash_seq_init(&hstat, PrivateRefCountHash);
-		while ((res = (PrivateRefCountEntry *) hash_seq_search(&hstat)) != NULL)
+		foreach_hash(PrivateRefCountEntry, ent, PrivateRefCountHash)
 		{
-			s = DebugPrintBufferRefcount(res->buffer);
+			s = DebugPrintBufferRefcount(ent->buffer);
 			elog(WARNING, "buffer refcount leak: %s", s);
 			pfree(s);
 			RefCountErrors++;
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 2d4c518dec2..8164a8c9174 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -539,8 +539,6 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 {
 #define PG_GET_SHMEM_SIZES_COLS 4
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hstat;
-	ShmemIndexEnt *ent;
 	Size		named_allocated = 0;
 	Datum		values[PG_GET_SHMEM_SIZES_COLS];
 	bool		nulls[PG_GET_SHMEM_SIZES_COLS];
@@ -549,11 +547,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 
 	LWLockAcquire(ShmemIndexLock, LW_SHARED);
 
-	hash_seq_init(&hstat, ShmemIndex);
-
 	/* output all allocated entries */
 	memset(nulls, 0, sizeof(nulls));
-	while ((ent = (ShmemIndexEnt *) hash_seq_search(&hstat)) != NULL)
+	foreach_hash(ShmemIndexEnt, ent, ShmemIndex)
 	{
 		values[0] = CStringGetTextDatum(ent->key);
 		values[1] = Int64GetDatum((char *) ent->location - (char *) ShmemSegHdr);
@@ -596,8 +592,6 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
 {
 #define PG_GET_SHMEM_NUMA_SIZES_COLS 3
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hstat;
-	ShmemIndexEnt *ent;
 	Datum		values[PG_GET_SHMEM_NUMA_SIZES_COLS];
 	bool		nulls[PG_GET_SHMEM_NUMA_SIZES_COLS];
 	Size		os_page_size;
@@ -647,11 +641,9 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
 
 	LWLockAcquire(ShmemIndexLock, LW_SHARED);
 
-	hash_seq_init(&hstat, ShmemIndex);
-
 	/* output all allocated entries */
 	memset(nulls, 0, sizeof(nulls));
-	while ((ent = (ShmemIndexEnt *) hash_seq_search(&hstat)) != NULL)
+	foreach_hash(ShmemIndexEnt, ent, ShmemIndex)
 	{
 		int			i;
 		char	   *startptr,
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 9305e009772..7d9e54be00c 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -1099,13 +1099,9 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
 void
 StandbyReleaseAllLocks(void)
 {
-	HASH_SEQ_STATUS status;
-	RecoveryLockXidEntry *entry;
-
 	elog(DEBUG2, "release all standby locks");
 
-	hash_seq_init(&status, RecoveryLockXidHash);
-	while ((entry = hash_seq_search(&status)))
+	foreach_hash(RecoveryLockXidEntry, entry, RecoveryLockXidHash)
 	{
 		StandbyReleaseXidEntryLocks(entry);
 		hash_search(RecoveryLockXidHash, entry, HASH_REMOVE, NULL);
@@ -1123,11 +1119,7 @@ StandbyReleaseAllLocks(void)
 void
 StandbyReleaseOldLocks(TransactionId oldxid)
 {
-	HASH_SEQ_STATUS status;
-	RecoveryLockXidEntry *entry;
-
-	hash_seq_init(&status, RecoveryLockXidHash);
-	while ((entry = hash_seq_search(&status)))
+	foreach_hash(RecoveryLockXidEntry, entry, RecoveryLockXidHash)
 	{
 		Assert(TransactionIdIsValid(entry->xid));
 
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index e5ea54e7824..02852ee1a4c 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -2292,11 +2292,9 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
 void
 LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 {
-	HASH_SEQ_STATUS status;
 	LockMethod	lockMethodTable;
 	int			i,
 				numLockModes;
-	LOCALLOCK  *locallock;
 	LOCK	   *lock;
 	int			partition;
 	bool		have_fast_path_lwlock = false;
@@ -2329,9 +2327,7 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 	 * pointers.  Fast-path locks are cleaned up during the locallock table
 	 * scan, though.
 	 */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		/*
 		 * If the LOCALLOCK entry is unused, something must've gone wrong
@@ -2566,15 +2562,10 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 void
 LockReleaseSession(LOCKMETHODID lockmethodid)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
-
 	if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
 		elog(ERROR, "unrecognized lock method: %d", lockmethodid);
 
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		/* Ignore items that are not of the specified lock method */
 		if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid)
@@ -2598,12 +2589,7 @@ LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks)
 {
 	if (locallocks == NULL)
 	{
-		HASH_SEQ_STATUS status;
-		LOCALLOCK  *locallock;
-
-		hash_seq_init(&status, LockMethodLocalHash);
-
-		while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 			ReleaseLockIfHeld(locallock, false);
 	}
 	else
@@ -2697,12 +2683,7 @@ LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks)
 
 	if (locallocks == NULL)
 	{
-		HASH_SEQ_STATUS status;
-		LOCALLOCK  *locallock;
-
-		hash_seq_init(&status, LockMethodLocalHash);
-
-		while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 			LockReassignOwner(locallock, parent);
 	}
 	else
@@ -3383,17 +3364,13 @@ CheckForSessionAndXactLocks(void)
 	} PerLockTagEntry;
 
 	HTAB	   *lockhtab;
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 
 	/* Create a local hash table keyed by LOCKTAG only */
 	lockhtab = hash_make(PerLockTagEntry, lock,
 						 "CheckForSessionAndXactLocks table", 256);
 
 	/* Scan local lock table to find entries for each LOCKTAG */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
 		PerLockTagEntry *hentry;
@@ -3456,16 +3433,11 @@ CheckForSessionAndXactLocks(void)
 void
 AtPrepare_Locks(void)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
-
 	/* First, verify there aren't locks of both xact and session level */
 	CheckForSessionAndXactLocks();
 
 	/* Now do the per-locallock cleanup work */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		TwoPhaseLockRecord record;
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
@@ -3553,8 +3525,6 @@ void
 PostPrepare_Locks(FullTransactionId fxid)
 {
 	PGPROC	   *newproc = TwoPhaseGetDummyProc(fxid, false);
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	LOCK	   *lock;
 	PROCLOCK   *proclock;
 	PROCLOCKTAG proclocktag;
@@ -3576,9 +3546,7 @@ PostPrepare_Locks(FullTransactionId fxid)
 	 * pointing to the same proclock, and we daren't end up with any dangling
 	 * pointers.
 	 */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
 		bool		haveSessionLock;
@@ -3774,8 +3742,6 @@ LockData *
 GetLockStatusData(void)
 {
 	LockData   *data;
-	PROCLOCK   *proclock;
-	HASH_SEQ_STATUS seqstat;
 	int			els;
 	int			el;
 	int			i;
@@ -3911,9 +3877,7 @@ GetLockStatusData(void)
 	}
 
 	/* Now scan the tables to copy the data */
-	hash_seq_init(&seqstat, LockMethodProcLockHash);
-
-	while ((proclock = (PROCLOCK *) hash_seq_search(&seqstat)))
+	foreach_hash(PROCLOCK, proclock, LockMethodProcLockHash)
 	{
 		PGPROC	   *proc = proclock->tag.myProc;
 		LOCK	   *lock = proclock->tag.myLock;
@@ -4152,8 +4116,6 @@ xl_standby_lock *
 GetRunningTransactionLocks(int *nlocks)
 {
 	xl_standby_lock *accessExclusiveLocks;
-	PROCLOCK   *proclock;
-	HASH_SEQ_STATUS seqstat;
 	int			i;
 	int			index;
 	int			els;
@@ -4175,10 +4137,9 @@ GetRunningTransactionLocks(int *nlocks)
 	 */
 	accessExclusiveLocks = palloc(els * sizeof(xl_standby_lock));
 
-	/* Now scan the tables to copy the data */
-	hash_seq_init(&seqstat, LockMethodProcLockHash);
-
 	/*
+	 * Now scan the tables to copy the data.
+	 *
 	 * If lock is a currently granted AccessExclusiveLock then it will have
 	 * just one proclock holder, so locks are never accessed twice in this
 	 * particular case. Don't copy this code for use elsewhere because in the
@@ -4186,7 +4147,7 @@ GetRunningTransactionLocks(int *nlocks)
 	 * non-exclusive lock types.
 	 */
 	index = 0;
-	while ((proclock = (PROCLOCK *) hash_seq_search(&seqstat)))
+	foreach_hash(PROCLOCK, proclock, LockMethodProcLockHash)
 	{
 		/* make sure this definition matches the one used in LockAcquire */
 		if ((proclock->holdMask & LOCKBIT_ON(AccessExclusiveLock)) &&
@@ -4281,18 +4242,14 @@ void
 DumpAllLocks(void)
 {
 	PGPROC	   *proc;
-	PROCLOCK   *proclock;
 	LOCK	   *lock;
-	HASH_SEQ_STATUS status;
 
 	proc = MyProc;
 
 	if (proc && proc->waitLock)
 		LOCK_PRINT("DumpAllLocks: waiting on", proc->waitLock, 0);
 
-	hash_seq_init(&status, LockMethodProcLockHash);
-
-	while ((proclock = (PROCLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PROCLOCK, proclock, LockMethodProcLockHash)
 	{
 		PROCLOCK_PRINT("DumpAllLocks", proclock);
 
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 1f749beadce..b7466d3ca18 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -316,15 +316,10 @@ init_lwlock_stats(void)
 static void
 print_lwlock_stats(int code, Datum arg)
 {
-	HASH_SEQ_STATUS scan;
-	lwlock_stats *lwstats;
-
-	hash_seq_init(&scan, lwlock_stats_htab);
-
 	/* Grab an LWLock to keep different backends from mixing reports */
 	LWLockAcquire(&MainLWLockArray[0].lock, LW_EXCLUSIVE);
 
-	while ((lwstats = (lwlock_stats *) hash_seq_search(&scan)) != NULL)
+	foreach_hash(lwlock_stats, lwstats, lwlock_stats_htab)
 	{
 		fprintf(stderr,
 				"PID %d lwlock %s %p: shacq %u exacq %u blk %u spindelay %u dequeue self %u\n",
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index c2d85032716..6b602ba89ea 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -1440,8 +1440,6 @@ GetPredicateLockStatusData(void)
 	int			i;
 	int			els,
 				el;
-	HASH_SEQ_STATUS seqstat;
-	PREDICATELOCK *predlock;
 
 	data = (PredicateLockData *) palloc(sizeof(PredicateLockData));
 
@@ -1463,11 +1461,9 @@ GetPredicateLockStatusData(void)

 	/* Scan through PredicateLockHash and copy contents */
-	hash_seq_init(&seqstat, PredicateLockHash);
-
 	el = 0;
 
-	while ((predlock = (PREDICATELOCK *) hash_seq_search(&seqstat)))
+	foreach_hash(PREDICATELOCK, predlock, PredicateLockHash)
 	{
 		data->locktags[el] = predlock->tag.myTarget->tag;
 		data->xacts[el] = *predlock->tag.myXact;
@@ -2924,8 +2920,6 @@ exit:
 static void
 DropAllPredicateLocksFromTable(Relation relation, bool transfer)
 {
-	HASH_SEQ_STATUS seqstat;
-	PREDICATELOCKTARGET *oldtarget;
 	PREDICATELOCKTARGET *heaptarget;
 	Oid			dbId;
 	Oid			relId;
@@ -2981,9 +2975,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer)
 		RemoveScratchTarget(true);
 
 	/* Scan through target map */
-	hash_seq_init(&seqstat, PredicateLockTargetHash);
-
-	while ((oldtarget = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat)))
+	foreach_hash(PREDICATELOCKTARGET, oldtarget, PredicateLockTargetHash)
 	{
 		dlist_mutable_iter iter;
 
@@ -4406,8 +4398,6 @@ CheckForSerializableConflictIn(Relation relation, const ItemPointerData *tid, Bl
 void
 CheckTableForSerializableConflictIn(Relation relation)
 {
-	HASH_SEQ_STATUS seqstat;
-	PREDICATELOCKTARGET *target;
 	Oid			dbId;
 	Oid			heapId;
 	int			i;
@@ -4441,9 +4431,7 @@ CheckTableForSerializableConflictIn(Relation relation)
 	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 
 	/* Scan through target list */
-	hash_seq_init(&seqstat, PredicateLockTargetHash);
-
-	while ((target = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat)))
+	foreach_hash(PREDICATELOCKTARGET, target, PredicateLockTargetHash)
 	{
 		dlist_mutable_iter iter;
 
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index d4fa6a144f1..b2ae58662f4 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -409,9 +409,6 @@ smgrdestroyall(void)
 void
 smgrreleaseall(void)
 {
-	HASH_SEQ_STATUS status;
-	SMgrRelation reln;
-
 	/* Nothing to do if hashtable not set up */
 	if (SMgrRelationHash == NULL)
 		return;
@@ -419,9 +416,7 @@ smgrreleaseall(void)
 	/* seems unsafe to accept interrupts while iterating */
 	HOLD_INTERRUPTS();
 
-	hash_seq_init(&status, SMgrRelationHash);
-
-	while ((reln = (SMgrRelation) hash_seq_search(&status)) != NULL)
+	foreach_hash(SMgrRelationData, reln, SMgrRelationHash)
 	{
 		smgrrelease(reln);
 	}
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 42b7fc11288..f65c52eb9a2 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -280,8 +280,6 @@ ProcessSyncRequests(void)
 {
 	static bool sync_in_progress = false;
 
-	HASH_SEQ_STATUS hstat;
-	PendingFsyncEntry *entry;
 	int			absorb_counter;
 
 	/* Statistics on sync times */
@@ -338,8 +336,7 @@ ProcessSyncRequests(void)
 	if (sync_in_progress)
 	{
 		/* prior try failed, so update any stale cycle_ctr values */
-		hash_seq_init(&hstat, pendingOps);
-		while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
+		foreach_hash(PendingFsyncEntry, entry, pendingOps)
 		{
 			entry->cycle_ctr = sync_cycle_ctr;
 		}
@@ -353,8 +350,7 @@ ProcessSyncRequests(void)
 
 	/* Now scan the hashtable for fsync requests to process */
 	absorb_counter = FSYNCS_PER_ABSORB;
-	hash_seq_init(&hstat, pendingOps);
-	while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
+	foreach_hash(PendingFsyncEntry, entry, pendingOps)
 	{
 		int			failures;
 
@@ -495,13 +491,10 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
 	}
 	else if (type == SYNC_FILTER_REQUEST)
 	{
-		HASH_SEQ_STATUS hstat;
-		PendingFsyncEntry *pfe;
 		ListCell   *cell;
 
 		/* Cancel matching fsync requests */
-		hash_seq_init(&hstat, pendingOps);
-		while ((pfe = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
+		foreach_hash(PendingFsyncEntry, pfe, pendingOps)
 		{
 			if (pfe->tag.handler == ftag->handler &&
 				syncsw[ftag->handler].sync_filetagmatches(ftag, &pfe->tag))
diff --git a/src/backend/tsearch/ts_typanalyze.c b/src/backend/tsearch/ts_typanalyze.c
index a9bc6901a12..439f8788003 100644
--- a/src/backend/tsearch/ts_typanalyze.c
+++ b/src/backend/tsearch/ts_typanalyze.c
@@ -149,7 +149,6 @@ compute_tsvector_stats(VacAttrStats *stats,
 
 	/* This is D from the LC algorithm. */
 	HTAB	   *lexemes_tab;
-	HASH_SEQ_STATUS scan_status;
 
 	/* This is the current bucket number from the LC algorithm */
 	int			b_current;
@@ -288,7 +287,6 @@ compute_tsvector_stats(VacAttrStats *stats,
 		int			nonnull_cnt = samplerows - null_cnt;
 		int			i;
 		TrackItem **sort_table;
-		TrackItem  *item;
 		int			track_len;
 		int			cutoff_freq;
 		int			minfreq,
@@ -315,10 +313,9 @@ compute_tsvector_stats(VacAttrStats *stats,
 		i = hash_get_num_entries(lexemes_tab);	/* surely enough space */
 		sort_table = (TrackItem **) palloc(sizeof(TrackItem *) * i);
 
-		hash_seq_init(&scan_status, lexemes_tab);
 		track_len = 0;
 		maxfreq = 0;
-		while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+		foreach_hash(TrackItem, item, lexemes_tab)
 		{
 			if (item->frequency > cutoff_freq)
 			{
@@ -462,11 +459,7 @@ compute_tsvector_stats(VacAttrStats *stats,
 static void
 prune_lexemes_hashtable(HTAB *lexemes_tab, int b_current)
 {
-	HASH_SEQ_STATUS scan_status;
-	TrackItem  *item;
-
-	hash_seq_init(&scan_status, lexemes_tab);
-	while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrackItem, item, lexemes_tab)
 	{
 		if (item->frequency + item->delta <= b_current)
 		{
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index 56a62029abb..a259f0b7058 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -299,8 +299,6 @@ char	  **
 GetWaitEventCustomNames(uint32 classId, int *nwaitevents)
 {
 	char	  **waiteventnames;
-	WaitEventCustomEntryByName *hentry;
-	HASH_SEQ_STATUS hash_seq;
 	int			index;
 	int			els;
 
@@ -313,10 +311,8 @@ GetWaitEventCustomNames(uint32 classId, int *nwaitevents)
 	waiteventnames = palloc(els * sizeof(char *));
 
 	/* Now scan the hash table to copy the data */
-	hash_seq_init(&hash_seq, WaitEventCustomHashByName);
-
 	index = 0;
-	while ((hentry = (WaitEventCustomEntryByName *) hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(WaitEventCustomEntryByName, hentry, WaitEventCustomHashByName)
 	{
 		if ((hentry->wait_event_info & WAIT_EVENT_CLASS_MASK) != classId)
 			continue;
diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c
index cc310c044b3..a5be7e4c1c3 100644
--- a/src/backend/utils/adt/array_typanalyze.c
+++ b/src/backend/utils/adt/array_typanalyze.c
@@ -223,7 +223,6 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 
 	/* This is D from the LC algorithm. */
 	HTAB	   *elements_tab;
-	HASH_SEQ_STATUS scan_status;
 
 	/* This is the current bucket number from the LC algorithm */
 	int			b_current;
@@ -232,10 +231,8 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 	int			bucket_width;
 	int			array_no;
 	int64		element_no;
-	TrackItem  *item;
 	int			slot_idx;
 	HTAB	   *count_tab;
-	DECountItem *count_item;
 
 	extra_data = (ArrayAnalyzeExtraData *) stats->extra_data;
 
@@ -300,6 +297,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		int64		prev_element_no = element_no;
 		int			distinct_count;
 		bool		count_item_found;
+		DECountItem *count_item;
 
 		vacuum_delay_point(true);
 
@@ -338,6 +336,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		{
 			Datum		elem_value;
 			bool		found;
+			TrackItem  *item;
 
 			/* No null element processing other than flag setting here */
 			if (elem_nulls[j])
@@ -458,10 +457,9 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		i = hash_get_num_entries(elements_tab); /* surely enough space */
 		sort_table = (TrackItem **) palloc(sizeof(TrackItem *) * i);
 
-		hash_seq_init(&scan_status, elements_tab);
 		track_len = 0;
 		maxfreq = 0;
-		while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+		foreach_hash(TrackItem, item, elements_tab)
 		{
 			if (item->frequency > cutoff_freq)
 			{
@@ -595,9 +593,8 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			 */
 			sorted_count_items = (DECountItem **)
 				palloc(sizeof(DECountItem *) * count_items_count);
-			hash_seq_init(&scan_status, count_tab);
 			j = 0;
-			while ((count_item = (DECountItem *) hash_seq_search(&scan_status)) != NULL)
+			foreach_hash(DECountItem, count_item, count_tab)
 			{
 				sorted_count_items[j++] = count_item;
 			}
@@ -684,11 +681,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 static void
 prune_element_hashtable(HTAB *elements_tab, int b_current)
 {
-	HASH_SEQ_STATUS scan_status;
-	TrackItem  *item;
-
-	hash_seq_init(&scan_status, elements_tab);
-	while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrackItem, item, elements_tab)
 	{
 		if (item->frequency + item->delta <= b_current)
 		{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b76d6bbd5c9..eb441a81552 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2990,8 +2990,6 @@ RelationCacheInvalidateEntry(Oid relationId)
 void
 RelationCacheInvalidate(bool debug_discard)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	Relation	relation;
 	List	   *rebuildFirstList = NIL;
 	List	   *rebuildList = NIL;
@@ -3004,9 +3002,7 @@ RelationCacheInvalidate(bool debug_discard)
 	RelationMapInvalidateAll();
 
 	/* Phase 1 */
-	hash_seq_init(&status, RelationIdCache);
-
-	while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 	{
 		relation = idhentry->reldesc;
 
@@ -3151,12 +3147,9 @@ AssertPendingSyncConsistency(Relation relation)
 void
 AssertPendingSyncs_RelationCache(void)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	Relation   *rels;
 	int			maxrels;
 	int			nrels;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3170,8 +3163,7 @@ AssertPendingSyncs_RelationCache(void)
 	maxrels = 1;
 	rels = palloc(maxrels * sizeof(*rels));
 	nrels = 0;
-	hash_seq_init(&status, GetLockMethodLocalHash());
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, GetLockMethodLocalHash())
 	{
 		Oid			relid;
 		Relation	r;
@@ -3193,8 +3185,7 @@ AssertPendingSyncs_RelationCache(void)
 		rels[nrels++] = r;
 	}
 
-	hash_seq_init(&status, RelationIdCache);
-	while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 		AssertPendingSyncConsistency(idhentry->reldesc);
 
 	for (i = 0; i < nrels; i++)
@@ -3222,8 +3213,6 @@ AssertPendingSyncs_RelationCache(void)
 void
 AtEOXact_RelationCache(bool isCommit)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3246,8 +3235,7 @@ AtEOXact_RelationCache(bool isCommit)
 	 */
 	if (eoxact_list_overflowed)
 	{
-		hash_seq_init(&status, RelationIdCache);
-		while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 		{
 			AtEOXact_cleanup(idhentry->reldesc, isCommit);
 		}
@@ -3256,10 +3244,11 @@ AtEOXact_RelationCache(bool isCommit)
 	{
 		for (i = 0; i < eoxact_list_len; i++)
 		{
-			idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
-													 &eoxact_list[i],
-													 HASH_FIND,
-													 NULL);
+			RelIdCacheEnt *idhentry = hash_search(RelationIdCache,
+												  &eoxact_list[i],
+												  HASH_FIND,
+												  NULL);
+
 			if (idhentry != NULL)
 				AtEOXact_cleanup(idhentry->reldesc, isCommit);
 		}
@@ -3375,8 +3364,6 @@ void
 AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 						  SubTransactionId parentSubid)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3394,8 +3381,7 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 	 */
 	if (eoxact_list_overflowed)
 	{
-		hash_seq_init(&status, RelationIdCache);
-		while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 		{
 			AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
 								mySubid, parentSubid);
@@ -3405,6 +3391,8 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 	{
 		for (i = 0; i < eoxact_list_len; i++)
 		{
+			RelIdCacheEnt *idhentry;
+
 			idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
 													 &eoxact_list[i],
 													 HASH_FIND,
@@ -6583,8 +6571,6 @@ write_relcache_init_file(bool shared)
 	char		tempfilename[MAXPGPATH];
 	char		finalfilename[MAXPGPATH];
 	int			magic;
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -6644,9 +6630,7 @@ write_relcache_init_file(bool shared)
 	/*
 	 * Write all the appropriate reldescs (in no particular order).
 	 */
-	hash_seq_init(&status, RelationIdCache);
-
-	while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 	{
 		Relation	rel = idhentry->reldesc;
 		Form_pg_class relform = rel->rd_rel;
diff --git a/src/backend/utils/cache/relfilenumbermap.c b/src/backend/utils/cache/relfilenumbermap.c
index 69aba463eae..2b9ca8b36a1 100644
--- a/src/backend/utils/cache/relfilenumbermap.c
+++ b/src/backend/utils/cache/relfilenumbermap.c
@@ -51,14 +51,10 @@ typedef struct
 static void
 RelfilenumberMapInvalidateCallback(Datum arg, Oid relid)
 {
-	HASH_SEQ_STATUS status;
-	RelfilenumberMapEntry *entry;
-
 	/* callback only gets registered after creating the hash */
 	Assert(RelfilenumberMapHash != NULL);
 
-	hash_seq_init(&status, RelfilenumberMapHash);
-	while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelfilenumberMapEntry, entry, RelfilenumberMapHash)
 	{
 		/*
 		 * If relid is InvalidOid, signaling a complete reset, we must remove
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index bcfd6a9f0d2..bd6a161e7de 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -55,11 +55,7 @@ typedef struct
 static void
 InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	TableSpaceCacheEntry *spc;
-
-	hash_seq_init(&status, TableSpaceCacheHash);
-	while ((spc = (TableSpaceCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(TableSpaceCacheEntry, spc, TableSpaceCacheHash)
 	{
 		if (spc->opts)
 			pfree(spc->opts);
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index cfa6d8b09a4..5446de5eb1a 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -94,11 +94,8 @@ static void
 InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
 {
 	HTAB	   *hash = (HTAB *) DatumGetPointer(arg);
-	HASH_SEQ_STATUS status;
-	TSAnyCacheEntry *entry;
 
-	hash_seq_init(&status, hash);
-	while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(TSAnyCacheEntry, entry, hash)
 		entry->isvalid = false;
 
 	/* Also invalidate the current-config cache if it's pg_ts_config */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f3897c953d3..f9ce7c5ddf3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -288,8 +288,6 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	const char *ConfFileWithError;
 	ConfigVariable *head,
 			   *tail;
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
 
 	/* Parse the main config file into a list of option names and values */
 	ConfFileWithError = ConfigFileName;
@@ -364,8 +362,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	 * need this so that we can tell below which ones have been removed from
 	 * the file since we last processed it.
 	 */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		struct config_generic *gconf = hentry->gucvar;
 
@@ -449,8 +446,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	 * boot-time defaults.  If such a variable can't be changed after startup,
 	 * report that and continue.
 	 */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		struct config_generic *gconf = hentry->gucvar;
 
@@ -839,8 +835,6 @@ struct config_generic **
 get_guc_variables(int *num_vars)
 {
 	struct config_generic **result;
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
 	int			i;
 
 	*num_vars = hash_get_num_entries(guc_hashtab);
@@ -848,8 +842,7 @@ get_guc_variables(int *num_vars)
 
 	/* Extract pointers from the hash table */
 	i = 0;
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 		result[i++] = hentry->gucvar;
 	Assert(i == *num_vars);
 
@@ -1400,9 +1393,6 @@ check_GUC_init(const struct config_generic *gconf)
 void
 InitializeGUCOptions(void)
 {
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
-
 	/*
 	 * Before log_line_prefix could possibly receive a nonempty setting, make
 	 * sure that timezone processing is minimally alive (see elog.c).
@@ -1418,8 +1408,7 @@ InitializeGUCOptions(void)
 	 * Load all variables with their compiled-in defaults, and initialize
 	 * status fields as needed.
 	 */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		/* Check mapping between initial and default value */
 		Assert(check_GUC_init(hentry->gucvar));
@@ -2414,9 +2403,6 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 void
 BeginReportingGUCOptions(void)
 {
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
-
 	/*
 	 * Don't do anything unless talking to an interactive frontend.
 	 */
@@ -2438,8 +2424,7 @@ BeginReportingGUCOptions(void)
 						PGC_INTERNAL, PGC_S_OVERRIDE);
 
 	/* Transmit initial values of interesting variables */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		struct config_generic *conf = hentry->gucvar;
 
@@ -5142,16 +5127,13 @@ void
 MarkGUCPrefixReserved(const char *className)
 {
 	int			classLen = strlen(className);
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
 	MemoryContext oldcontext;
 
 	/*
 	 * Check for existing placeholders.  We must actually remove invalid
 	 * placeholders, else future parallel worker startups will fail.
 	 */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		struct config_generic *var = hentry->gucvar;
 
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 8241d32f0c6..4a7b10d219b 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -776,12 +776,7 @@ PreCommit_Portals(bool isPrepare)
 void
 AtAbort_Portals(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -853,12 +848,7 @@ AtAbort_Portals(void)
 void
 AtCleanup_Portals(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -912,12 +902,7 @@ AtCleanup_Portals(void)
 void
 PortalErrorCleanup(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -941,12 +926,7 @@ AtSubCommit_Portals(SubTransactionId mySubid,
 					int parentLevel,
 					ResourceOwner parentXactOwner)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -977,12 +957,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 				   ResourceOwner myXactOwner,
 				   ResourceOwner parentXactOwner)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -1087,12 +1062,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 void
 AtSubCleanup_Portals(SubTransactionId mySubid)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -1127,8 +1097,6 @@ Datum
 pg_cursor(PG_FUNCTION_ARGS)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hash_seq;
-	PortalHashEnt *hentry;
 
 	/*
 	 * We put all the tuples into a tuplestore in one scan of the hashtable.
@@ -1136,8 +1104,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 	 */
 	InitMaterializedSRF(fcinfo, 0);
 
-	hash_seq_init(&hash_seq, PortalHashTable);
-	while ((hentry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 		Datum		values[6];
@@ -1166,12 +1133,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 bool
 ThereAreNoReadyPortals(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -1202,12 +1164,7 @@ ThereAreNoReadyPortals(void)
 void
 HoldPinnedPortals(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -1251,15 +1208,11 @@ HoldPinnedPortals(void)
 void
 ForgetPortalSnapshots(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
 	int			numPortalSnaps = 0;
 	int			numActiveSnaps = 0;
 
 	/* First, scan PortalHashTable and clear portalSnapshot fields */
-	hash_seq_init(&status, PortalHashTable);
-
-	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
-- 
2.52.0

---

From: Thomas Munro <[email-masked]>
Date: Fri, 5 Dec 2025 14:29:48 +1300

> HASH_BLOBS anyway for a char array (in case it's not null terminated).

How much of our header stuff is supposed to work from C++ too?  I
assume that if this is passing cpluspluscheck then it's only because
those _Generic macros aren't being expanded.  I suppose you could
write the typeof-based version you already hinted at, but only use it
for __cplusplus__ (where typeof exists as decltype).

What I mean is, if something like DuckDB (C++ IIRC) wants to actually
use these APIs with the new interfaces, they won't work if the macros
expant to _Generic (not legal C++), but a typeof/decltype-based
version should work just fine, but at the same time I don't think
we'd want to use typeof just because it is available, or we'd never
test the _Generic version: all 3 C compilers have something we can use
for typeof, it's just not standard C before C23, and we want
PostgreSQL to be a valid C11 program and actually have coverage of
those code branches.

Another consideration is what impact we have on the Rust world, and
potentially other languages used for extensions that call C via FFI
etc, if we start using _Generic in our public interfaces, as opposed
to just using it in smaller ways as part of our internal assertions
and C implementation code that doesn't affect extensions.  Of course
people using C APIs have to deal with the complexities of C evolution
too, I'm not saying that's a blocker, I'm just trying to think through
the impact of these sorts of API changes on the ecosystem.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:32:32 +0100

On Fri, 5 Dec 2025 at 02:30, Thomas Munro <[email-masked]> wrote:
> How much of our header stuff is supposed to work from C++ too?

I think it's nice if it works, but it doesn't seem the most important.
Especially since C++ has its own hashmaps. And if it really needs to
create a hashmap it's still possible to call the.

> I suppose you could

I tried to figure something out that would work in C++ (with help of
Claude), but I wasn't able to create a version of the macros without
also needing to add:

#ifdef __cplusplus
}
#include <type_traits>
extern "C" {
#endif

It seems quite ugly to escape the extern "C" from the parent like that
and then re-enter it. Overall it doesn't seem worth the hassle to me
to make these macros work in C++.

> etc

FFI generally cannot call macros anyway, only actual symbols.

---

From: Thomas Munro <[email-masked]>
Date: Sat, 6 Dec 2025 13:56:32 +1300

> create a hashmap it's still possible to call the.

... C functions without the helper macros.  Yeah.  That seems OK to me.

> to make these macros work in C++.

Yeah.  I don't think we want that sort of thing all over the place.
We could eventually come up with a small set of tools in a central
place though, so people can work with this stuff without also known
C++ meta-programming voodoo.  For example something like (untested, I
didn't think about char[size], just spitballing here...):

(pg_expr_has_type_p(ptr, char *) || pg_expr_has_type_p(ptr, NameData *))

... given the definition I posted recently[1].

I take your point that it's not really important for this case though.

> FFI generally cannot call macros anyway, only actual symbols.

Sure, I was just thinking about how such cross-language usage would be
forced to unpick our macrology and call the underlying C functions
without them.  Doesn't seem like the end of the world anyway, I was
just thinking out loud about the consequences of this phenomenon in
headers.

[1] https://www.postgresql.org/message-id/[email-masked]

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Mon, 08 Dec 2025 11:53:02 +0100

On Sat Dec 6, 2025 at 1:56 AM CET, Thomas Munro wrote:
> ... C functions without the helper macros.

Oops, forgot to finish that sentence.
> ... given the definition I posted recently[1].

Ugh... It would have saved me some time if I'd seen that email before. I
also had no clue that 'extern "C++"' was a thing. Attached is a new
patchset where your proposed macro is used. I also needed a
pg_nullptr_of macro, because the trick that worked in C didn't in C++.

Also, it's probably worth checking out this thread[2]. Especially
because it overlaps significantly with[1].

> just thinking out loud about the consequences of this phenomenon in

It's not great, but yeah that's the situation. Stuff like PG_TRY and
PG_CATCH are especially painful to reimplement. Luckily those things
can usually be re-implemented similarly in the target language, so the
macros only need to be disected once.

> [1] https://www.postgresql.org/message-id/[email-masked]
[2]: https://www.postgresql.org/message-id/flat/CAGECzQR21OnnKiZO_1rLWO0-16kg1JBxnVq-wymYW0-_1cUNtg%40mail.gmail.com
From 5288920fd80a612d5660fb576dcf80bb6461ab0e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v3 1/5] Add hash_make macros

Co-Authored-By: Bertrand Drouvot <[email-masked]>
---
 src/backend/utils/hash/dynahash.c | 109 ++++++++++++++++++++
 src/include/c.h                   |  31 ++++++
 src/include/storage/shmem.h       |  32 ++++++
 src/include/utils/hsearch.h       | 160 +++++++++++++++++++++++++++++-
 src/tools/pgindent/typedefs.list  |   1 +
 5 files changed, 328 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index ac94b9e93c6..bc47469ab3c 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -631,6 +631,115 @@ hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
 	return hashp;
 }
 
+/*
+		/* force_blobs only affects default hash selection, not custom hash */
+		Assert(!opts->force_blobs);
+	else if (opts != NULL && opts->force_blobs)
+		*flags |= HASH_BLOBS;
diff --git a/src/include/c.h b/src/include/c.h
index ccd2b654d45..e3c86d68932 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -81,6 +81,11 @@
 #endif
 #ifdef ENABLE_NLS
 #include <libintl.h>
+#endif
+#ifdef __cplusplus
+extern "C++" {
+#include <type_traits>
+}
 #endif
 
  /* Pull in fundamental symbols that we also expose to applications */
@@ -362,6 +367,32 @@
 #define HAVE_PG_INTEGER_CONSTANT_P
 #endif
 
+/*
+ * pg_expr_has_type_p(expr, type) - Check if an expression has a specific type.
+ *
+ * Similar to pg_is_same, but takes an expression instead of a type as the
+ * first argument. This is useful when you have an expression and want to
+ * check its type without needing typeof/decltype.
+ */
+#if defined(__cplusplus)
+#define pg_expr_has_type_p(expr, type) (std::is_same<decltype(expr), type>::value)
+#else
+#define pg_expr_has_type_p(expr, type) \
+	_Generic((expr), type: 1, default: 0)
+#endif
+
+/*
+ * pg_nullptr_of(type) - Create a null pointer of the given type.
+ *
+ * In C, (type *)NULL works for all types. In C++, this syntax fails for types
+ * containing brackets (like char[64]), so we use std::add_pointer_t instead.
+ */
+#if defined(__cplusplus)
+#define pg_nullptr_of(type) (static_cast<std::add_pointer_t<type>>(nullptr))
+#else
+#define pg_nullptr_of(type) ((type *)NULL)
+#endif
+
 /*
  * pg_assume(expr) states that we assume `expr` to evaluate to true. In assert
  * enabled builds pg_assume() is turned into an assertion, in optimized builds
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 70a5b8b172c..74f61aebc1a 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -43,6 +43,38 @@ extern Size mul_size(Size s1, Size s2);

+/*
 
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index cb09a4cbe8c..98c88726345 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -15,6 +15,9 @@
 #define HSEARCH_H

@@ -43,6 +46,150 @@ typedef void *(*HashCopyFunc) (void *dest, const void *src, Size keysize);
 
+/*
+	bool		force_blobs;	/* if true, use HASH_BLOBS even for string
+								 * types */
+	(pg_expr_has_type_p((ptr), char(*)[size]) || pg_expr_has_type_p((ptr), NameData *))
+	HASH_PTR_AS_STRING(pg_nullptr_of(type), sizeof(type))
@@ -57,11 +204,11 @@ typedef struct HASHELEMENT
 
@@ -131,6 +278,9 @@ typedef struct
index 6e2ed0c8825..73601910642 100644
@@ -1145,6 +1145,7 @@ HASHBUCKET
 HASHCTL
 HASHELEMENT
 HASHHDR
+HASHOPTS
 HASHSEGMENT
 HASH_SEQ_STATUS
 HE

base-commit: 31280d96a64850f5a9a924088890ab43a2905237
-- 
2.52.0

From 26faf1d5ab11a0782d5c6c8f48f9415701599771 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:36:19 +0100
Subject: [PATCH v3 2/5] Use hash_make macros throughout the codebase

 
 static remoteConn *

 
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index 1a76794c422..df7d3b70d04 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -893,7 +893,6 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 static void
 transformGraph(TrgmNFA *trgmNFA)
 {
-	HASHCTL		hashCtl;
 	TrgmStateKey initkey;
 	TrgmState  *initstate;
 	ListCell   *lc;
@@ -905,13 +904,7 @@ transformGraph(TrgmNFA *trgmNFA)
 	trgmNFA->overflowed = false;

 
 
 		/*

index c40fd36dc96..519855189cf 100644

index 1967b047020..98793a3f2eb 100644
 init_missing_cache(void)

 static void

 

 
 
 	/*

 

 	/*

 /*

 

 
 /*
 
 /*

 	/*

 

 
 
 /*
 
 /*

 

 	if (*p_htable == NULL)
 	{
-		HASHCTL		ctl;
-

 	/*

 

 

 

 	/*

 
 	/*
index a4eb3962cb1..eadf5b67eb5 100644

 

 

 

 

 
 /*
index 6bb0cbeedad..276b746b4db 100644
 
 	/*

 

 
 
 /*
index ce52d6ca81f..781738956af 100644

-	hash_ctl.keysize = sizeof(Buffer);
 
 	/*
 
 	/*

 	if (!LocalBufHash)
 
 		/*

 /*
 
+/*
index fc45d72c79b..7b9f7e218aa 100644

 
 	/*

 	/*

-	HASHCTL		hash_ctl;
 	HTAB	   *lockhtab;
 	HASH_SEQ_STATUS status;
 	LOCALLOCK  *locallock;

 

 
 	/*

 	/*
 
 	/*
 
 /*

 /*

 

index 96d61f77f6e..35aa0208fc9 100644

 
 
 /*

 

 
 static void
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 22de18bc5b9..b369a9e3b16 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3808,18 +3808,12 @@ static HTAB *
 get_json_object_as_hash(const char *json, int len, const char *funcname,
 						Node *escontext)
 {
-	HASHCTL		ctl;
 	HTAB	   *tab;
 	JHashState *state;
 	JsonSemAction *sem;

@@ -4213,7 +4207,6 @@ populate_recordset_object_start(void *state)
 
 	/* Reject object at top level: we must have an array at level 0 */
 	if (lex_level == 0)
@@ -4227,13 +4220,8 @@ populate_recordset_object_start(void *state)
 		return JSON_SUCCESS;

 	return JSON_SUCCESS;
 }

 

 

 	/*

index 76ba2db5390..f5ce873f43c 100644

 	/*

 /*

 
 
 	/*
 
 	/*

 

 

 
 
 /*

 

 

 /*

 	/*
 
 	if (inited)
 		return;
 
 	/*
 
 	/*

 

 

 /*

 

-- 
2.52.0

From eded86a9b53aa33bf2269607d344d5264c663d16 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 3 Dec 2025 23:33:18 +0100
Subject: [PATCH v3 3/5] Inline functions that have now become trivial

---

 
-
-/*

 
-/*

 

-/*

From b59b950e9ec6dad9513edb2f4eed5bfd64a3dd8a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:00 +0100
Subject: [PATCH v3 4/5] Add foreach_hash macro

For lists we've had a new foreach style macros since 14dd0f27d7. This
adds a similar macro for hash tables. This new foreach_hash macro makes
iterating over the items in an HTAB as simple as iterating over the
items in a List. The only additional thing to keep in mind is that when
exiting the loop early you need to call foreach_hash_term.
---
 src/backend/utils/hash/dynahash.c | 15 +++++++++-
 src/include/utils/hsearch.h       | 46 +++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index bc47469ab3c..50be734624a 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -1462,7 +1462,7 @@ hash_get_num_entries(HTAB *hashp)
 }
 
 /*
- * hash_seq_init/_search/_term
+ * hash_seq_init/_new/_search/_term
  *			Sequentially search through hash table and return
  *			all the elements one by one, return NULL when no more.
  *
@@ -1496,6 +1496,19 @@ hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp)
 		register_seq_scan(hashp);
 }
 
+/*
+ * Same as hash_seq_init(), but returns the status struct instead of taking a
+ * pointer.
+ */
+HASH_SEQ_STATUS
+hash_seq_new(HTAB *hashp)
+{
+	HASH_SEQ_STATUS status;
+
+	hash_seq_init(&status, hashp);
+	return status;
+}
+
 /*
  * Same as above but scan by the given hash value.
  * See also hash_seq_search().
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 98c88726345..957680d7a9b 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -273,6 +273,51 @@ typedef struct
 	uint32		hashvalue;		/* hashvalue to start seqscan over hash */
 } HASH_SEQ_STATUS;
 
+/*
+ * foreach_hash - iterate over all entries in a hash table
+ *
+ * This macro simplifies hash table iteration by combining hash_seq_init
+ * and hash_seq_search into a single for-loop construct.
+ *
+ * Usage:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * This replaces the more verbose pattern:
+ *   HASH_SEQ_STATUS status;
+ *   MyEntry *entry;
+ *   hash_seq_init(&status, my_hashtable);
+ *   while ((entry = (MyEntry *) hash_seq_search(&status)) != NULL)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * For early termination, use foreach_hash_term() before break:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       if (found_it)
+ *       {
+ *           foreach_hash_term(entry);
+ *           break;
+ *       }
+ *   }
+ */
+#define foreach_hash(type, var, htab) \
+	for (type *var = 0, *var##__outerloop = (type *) 1; \
+		 var##__outerloop; \
+		 var##__outerloop = 0) \
+		for (HASH_SEQ_STATUS var##__status = hash_seq_new(htab); \
+			 (var = (type *) hash_seq_search(&var##__status)) != NULL; )
+
+/*
+ * foreach_hash_term - terminate a foreach_hash loop early
+ *
+ * Call this before 'break' to properly clean up the hash scan.
+ */
+#define foreach_hash_term(var) hash_seq_term(&var##__status)
+
 /*
  * prototypes for functions in dynahash.c
  */
@@ -293,6 +338,7 @@ extern bool hash_update_hash_key(HTAB *hashp, void *existingEntry,
 								 const void *newKeyPtr);
 extern int64 hash_get_num_entries(HTAB *hashp);
 extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp);
+extern HASH_SEQ_STATUS hash_seq_new(HTAB *hashp);
 extern void hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status,
 										  HTAB *hashp,
 										  uint32 hashvalue);
-- 
2.52.0

From b01d33106c75ae993657aae65eddb6c9600df452 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:09 +0100
Subject: [PATCH v3 5/5] Use foreach_hash macro throughout the codebase

 
 	if (remoteConnHash)
 	{

 	/*

 

index df7d3b70d04..53a9f885ec2 100644
@@ -1449,10 +1449,8 @@ prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2)
@@ -1463,8 +1461,7 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
 
 	i = 0;
 
@@ -1926,8 +1923,6 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 	int			snumber = 2,
 				arcIndex,
 				arcsCount;
-	HASH_SEQ_STATUS scan_status;
-	TrgmState  *state;
 	TrgmPackArcInfo *arcs;
 	TrgmPackedArc *packedArcs;
 	TrgmPackedGraph *result;
@@ -1935,8 +1930,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 				j;
 
 	/* Enumerate surviving states, giving init and fin reserved numbers */
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		while (state->parent)
 			state = state->parent;
@@ -1958,8 +1952,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 	/* Collect array of all arcs */
 	arcs = palloc_array(TrgmPackArcInfo, trgmNFA->arcsCount);
 	arcIndex = 0;
-	hash_seq_init(&scan_status, trgmNFA->states);
-	while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrgmState, state, trgmNFA->states)
 	{
 		TrgmState  *source = state;
 		ListCell   *cell;
@@ -2202,16 +2195,13 @@ static void
 printTrgmNFA(TrgmNFA *trgmNFA)
 {
 	StringInfoData buf;
-	HASH_SEQ_STATUS scan_status;
-	TrgmState  *state;
 	TrgmState  *initstate = NULL;
 
 	initStringInfo(&buf);

 

 

 

 

 

 

 
 
 	if (!pendingSyncHash)

 
 
 	hash_destroy(tmphash);

 

 	/* walk over cache */

 

 
 		i = 0;
 
 		/* sort the array */

 

 

 
index 781738956af..edcf917fb7d 100644
 
 	/* check the array */

 

index 7b9f7e218aa..c63feaa9dd7 100644

 
 
 	if (locallocks == NULL)
 	{
 
 	HTAB	   *lockhtab;

 

 
 	proc = MyProc;

 

 

 

 

-	HASH_SEQ_STATUS hstat;

 

 
 
index 35aa0208fc9..d0b90f40d32 100644

 

 

 

 	/* Phase 1 */

 	/*

 
 	/*
 
 	/*
 
 	/*

 

 

 

 
 	/*

 

 

 	/*

 

-- 
2.52.0

---

From: Bertrand Drouvot <[email-masked]>
Date: Tue, 9 Dec 2025 07:27:03 +0000

Hi,

> Oops, forgot to finish that sentence.

Thanks for this patch series!

What about relying on the existing __builtin_types_compatible_p() instead of
_Generic() here? 

Regards,

---

From: Thomas Munro <[email-masked]>
Date: Tue, 9 Dec 2025 22:10:39 +1300

> What about relying on the existing __builtin_types_compatible_p() instead of

If we used standard C/C++ it'd work on MSVC too.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Tue, 9 Dec 2025 11:43:01 +0100

> If we used standard C/C++ it'd work on MSVC too.

And to be clear, that's important because the result of
pg_expr_has_type_p fundamentally impacts the meaning of the code (i.e.
it determines the hash function). Our existing usage of
__builtin_types_compatible_p only adds some *optional* type checking,
so for that it's not critical that it works on MSVC too.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Tue, 9 Dec 2025 11:45:11 +0100

> Thanks for this patch series!

To clarify: Does that mean +1 from you on the proposed API?

---

From: Bertrand Drouvot <[email-masked]>
Date: Tue, 9 Dec 2025 14:15:30 +0000

Hi,

> To clarify: Does that mean +1 from you on the proposed API?

Yeah, I think the hash table API needs improvement. I did not look at all the
details of your patches, but from what I have seen I think your API proposal
make sense.

Regards,

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sat, 14 Mar 2026 14:48:58 +0100

On Tue Jan 27, 2026 at 7:10 PM CET, Jelte Fennema-Nio wrote:
> Good suggestion. Done.

Newly attached version that fixes a rebase conclict. Including a few
small additions:

1. Use foreach_hash in async.c (in recently introduced code from
   282b1cde9ded)
2. Reference foreach_hash in code comments instead of hash_seq_search
3. Add a note about the reason for the double for loop in foreach_hash
   macro code comment (same comment as for foreach_internal in
   pg_list.h)

From 6fc5e3d84893748f31e3f96e87271137fe2198cf Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v11 1/5] Add hash_make macros

Co-Authored-By: Bertrand Drouvot <[email-masked]>
---
 doc/src/sgml/system-views.sgml    |   5 +-
 src/backend/utils/hash/dynahash.c | 115 +++++++++++++++++++++
 src/include/c.h                   |  32 ++++++
 src/include/storage/shmem.h       |  32 ++++++
 src/include/utils/hsearch.h       | 166 +++++++++++++++++++++++++++++-
 src/tools/pgindent/typedefs.list  |   1 +
 6 files changed, 345 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index e5fe423fc61..006b29005a2 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4254,7 +4254,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
   <para>
    Anonymous allocations are allocations that have been made
    with <literal>ShmemAlloc()</literal> directly, rather than via
-   <literal>ShmemInitStruct()</literal> or
+   <literal>ShmemInitStruct()</literal>,
+   <literal>shmem_hash_make()</literal>,
+   <literal>shmem_hash_make_ext()</literal>,
+   or
    <literal>ShmemInitHash()</literal>.
   </para>
 
diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index 11f5778eba1..f9b7f1669fa 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -631,6 +631,121 @@ hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
 	return hashp;
 }
 
+/*
+	if (opts == NULL)
+		return;
+	if (opts->hash != NULL)
+	else if (opts->force_blobs)
+	if (opts->match != NULL)
+	if (opts->keycopy != NULL)
+	if (opts->alloc != NULL)
+	if (opts->num_partitions > 0)
+	if (opts->fixed_size)
index 2aab74d8b0e..3d99e44c5b9 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -83,6 +83,12 @@
 #ifdef ENABLE_NLS
 #include <libintl.h>
 #endif
+#ifdef __cplusplus
+extern "C++"
+{
+#include <type_traits>
+}
+#endif
 
 #ifdef __cplusplus
 extern "C++"
@@ -387,6 +393,32 @@ extern "C++"
 #define HAVE_PG_INTEGER_CONSTANT_P
 #endif
 
+/*
+ * In C, (type *) NULL works for all types. In C++, this syntax fails for types
+#define pg_nullptr_of(type) ((type *) NULL)
index 89d45287c17..da1d901296b 100644
@@ -42,6 +42,38 @@ extern Size mul_size(Size s1, Size s2);

+/*
 
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index cdc530fb732..71c9780132f 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -15,6 +15,9 @@
 #define HSEARCH_H

@@ -43,6 +46,156 @@ typedef void *(*HashCopyFunc) (void *dest, const void *src, Size keysize);
 
+/*
+ * NOTE: If you use char[N] to store binary data that might contain null bytes
+ * and/or is not null terminated, the automatic detection will incorrectly
+ * treat it as a string and use string comparison. In such cases, use
+ * hash_make_ext() with .force_blobs = true to override the automatic
+ * detection.
@@ -57,11 +210,11 @@ typedef struct HASHELEMENT
 
@@ -131,6 +284,9 @@ typedef struct
index ec8513d90b5..18484eb3d80 100644
@@ -1163,6 +1163,7 @@ HASHBUCKET
 HASHCTL
 HASHELEMENT
 HASHHDR
+HASHOPTS
 HASHSEGMENT
 HASH_SEQ_STATUS
 HE

base-commit: ae58189a4d523f0156ebe30f4534180555669e88
-- 
2.53.0

From 8c536a27547dd294d3c9010bf037d48c7d611cba Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:00 +0100
Subject: [PATCH v11 2/5] Add foreach_hash macro

For lists we've had a new foreach style macros since 14dd0f27d7. This
adds a similar macro for hash tables. This new foreach_hash macro makes
iterating over the items in an HTAB as simple as iterating over the
items in a List. The only additional thing to keep in mind is that when
exiting the loop early you need to call foreach_hash_term.
---
 src/include/utils/hsearch.h | 81 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 80 insertions(+), 1 deletion(-)

diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 71c9780132f..8d5c218ff4f 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -279,6 +279,86 @@ typedef struct
 	uint32		hashvalue;		/* hashvalue to start seqscan over hash */
 } HASH_SEQ_STATUS;
 
+extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp);
+
+/*
+ * Same as hash_seq_init(), but returns the status struct instead of taking a
+ * pointer. This way we can use it in the initialization clause of a for loop,
+ * which we need in the foreach_hash macro.
+ *
+ * Not intended to be called directly by user code.
+ */
+static inline HASH_SEQ_STATUS
+foreach_hash_start(HTAB *hashp)
+{
+	HASH_SEQ_STATUS status;
+
+	hash_seq_init(&status, hashp);
+	return status;
+}
+
+
+/*
+ * foreach_hash - iterate over all entries in a hash table
+ *
+ * This macro simplifies hash table iteration by combining hash_seq_init
+ * and hash_seq_search into a single for-loop construct.
+ *
+ * Usage:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * This replaces the more verbose pattern:
+ *   HASH_SEQ_STATUS status;
+ *   MyEntry *entry;
+ *   hash_seq_init(&status, my_hashtable);
+ *   while ((entry = (MyEntry *) hash_seq_search(&status)) != NULL)
+ *   {
+ *       // use entry
+ *   }
+ *
+ * For early termination, use foreach_hash_term() before break:
+ *   foreach_hash(MyEntry, entry, my_hashtable)
+ *   {
+ *       if (found_it)
+ *       {
+ *           foreach_hash_term(entry);
+ *           break;
+ *       }
+ *   }
+ *
+ * This macro actually generates two loops in order to declare two variables of
+ * different types.  The outer loop only iterates once, so we expect optimizing
+ * compilers will unroll it, thereby optimizing it away. (This is the same
+ * trick that's used in the foreach_internal macro in pg_list.h)
+ */
+#define foreach_hash(type, var, htab) \
+	for (type *var = NULL, *var##__outerloop = (type *) 1; \
+		 var##__outerloop; \
+		 var##__outerloop = 0) \
+		for (HASH_SEQ_STATUS var##__status = foreach_hash_start(htab); \
+			 (var = (type *) hash_seq_search(&var##__status)) != NULL; )
+
+/*
+ * foreach_hash_term - terminate a foreach_hash loop early
+ *
+ * Always call this before breaking out of a foreach_hash loop, whether done by
+ * using "break", "return" or "goto". (Not needed when using "continue")
+ */
+#define foreach_hash_term(var) hash_seq_term(&var##__status)
+
+/*
+ * foreach_hash_restart - restart iteration from the beginning
+ *
+ * Use when modifications during iteration may have invalidated the scan.
+ * The next iteration will start from the first entry again.
+ */
+#define foreach_hash_restart(var, htab) \
+	(hash_seq_term(&var##__status), \
+	 hash_seq_init(&var##__status, htab))
+
 /*
  * prototypes for functions in dynahash.c
  */
@@ -298,7 +378,6 @@ extern void *hash_search_with_hash_value(HTAB *hashp, const void *keyPtr,
 extern bool hash_update_hash_key(HTAB *hashp, void *existingEntry,
 								 const void *newKeyPtr);
 extern int64 hash_get_num_entries(HTAB *hashp);
-extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp);
 extern void hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status,
 										  HTAB *hashp,
 										  uint32 hashvalue);
-- 
2.53.0

From 54c0802595663eed830e33c164f7777f153b5b62 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:36:19 +0100
Subject: [PATCH v11 3/5] Use hash_make macros throughout the codebase

Do note that these new macros are not just quality-of-life, but they
also calculate the key and entry sizes automatically. Thus making them
less error prone to maintain in case of changes to the underlying
structs.
---
 src/backend/commands/async.c                  | 49 ++++----------
 72 files changed, 376 insertions(+), 854 deletions(-)

index 2498d80c8e7..36d4c3a1847 100644
@@ -2542,13 +2542,9 @@ getConnectionByName(const char *name)
 
 static remoteConn *
index 4a427533bd8..8a67390e561 100644

 
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index b4eaeec6090..383ce6b31b1 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -896,7 +896,6 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 static void
 transformGraph(TrgmNFA *trgmNFA)
 {
-	HASHCTL		hashCtl;
 	TrgmStateKey initkey;
 	TrgmState  *initstate;
 	ListCell   *lc;
@@ -908,13 +907,7 @@ transformGraph(TrgmNFA *trgmNFA)
 	trgmNFA->overflowed = false;

index 7e2b822d161..975394fa3b1 100644
@@ -216,17 +216,13 @@ GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state)

 		/*
index 250f54fea32..cba1c0967a9 100644
 
@@ -91,13 +92,10 @@ InvalidateShippableCacheCallback(Datum arg, SysCacheIdentifier cacheid,
 
index ca2434c6e19..426f4b236a2 100644

index 11bec20e82e..e648001f11b 100644
 
index 7f57c787f4c..09f11c459d7 100644
 
 static void
index 3213cf45aa6..029af8542f9 100644
 
 	gfbb = palloc_object(GISTBuildBuffers);

index 8e220a3ae16..99eb291e832 100644

 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..33be08de362 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -238,7 +238,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	RewriteState state;
 	MemoryContext rw_cxt;
 	MemoryContext old_cxt;
-	HASHCTL		hash_ctl;
 
 	/*
 	 * To ease cleanup, make a separate context that will contain the
@@ -263,24 +262,19 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	state->rs_cxt = rw_cxt;
 	state->rs_bulkstate = smgr_bulk_start_rel(new_heap, MAIN_FORKNUM);

 
 
@@ -761,7 +755,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 static void
 logical_begin_heap_rewrite(RewriteState state)
 {
-	HASHCTL		hash_ctl;
 	TransactionId logical_xmin;
 
 	/*
@@ -792,15 +785,11 @@ logical_begin_heap_rewrite(RewriteState state)
 	state->rs_begin_lsn = GetXLogInsertRecPtr();
 	state->rs_num_rewrite_mappings = 0;

 /*
index 24cfa96d737..fee40afc765 100644
 
 	prefetcher = palloc0_object(XLogPrefetcher);
 	prefetcher->reader = reader;

index 5fbe39133b8..d11e42c9490 100644

index 33a461484d4..86c8bada557 100644
 
 /*
 
 /*
index cef80633157..537769b39ae 100644

 	/*
index e443a4993c5..db3e08319b5 100644

 
 
 	/* collect all rlocator from pending syncs */
 	hash_seq_init(&scan, pendingSyncHash);
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 5c9a56c3d40..0c21f528498 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -737,21 +737,15 @@ initGlobalChannelTable(void)
 static void
 initLocalChannelTable(void)
 {
-	HASHCTL		hash_ctl;
-
 	/* Quick exit if we already did this */
 	if (localChannelTable != NULL)
 		return;
 
 	/* Initialize local hash table for this backend's listened channels */
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = sizeof(ChannelName);
-
 	localChannelTable =
-		hash_create("Local Listen Channels",
-					64,
-					&hash_ctl,
-					HASH_ELEM | HASH_STRINGS);
+		hash_make_cxt(ChannelName, channel, "Local Listen Channels",
+					  64,
+					  TopMemoryContext);
 }
 
 /*
@@ -763,20 +757,13 @@ initLocalChannelTable(void)
 static void
 initPendingListenActions(void)
 {
-	HASHCTL		hash_ctl;
-
 	if (pendingListenActions != NULL)
 		return;
 
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = sizeof(PendingListenEntry);
-	hash_ctl.hcxt = CurTransactionContext;
-
 	pendingListenActions =
-		hash_create("Pending Listen Actions",
-					list_length(pendingActions->actions),
-					&hash_ctl,
-					HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+		hash_make_cxt(PendingListenEntry, channel, "Pending Listen Actions",
+					  list_length(pendingActions->actions),
+					  CurTransactionContext);
 }
 
 /*
@@ -3171,31 +3158,21 @@ AddEventToPendingNotifies(Notification *n)
 	if (list_length(pendingNotifies->events) >= MIN_HASHABLE_NOTIFIES &&
 		pendingNotifies->hashtab == NULL)
 	{
-		HASHCTL		hash_ctl;
 		ListCell   *l;

 		/* Create the unique channel name table */
 		Assert(pendingNotifies->uniqueChannelHash == NULL);
-		hash_ctl.keysize = NAMEDATALEN;
-		hash_ctl.entrysize = sizeof(ChannelName);
-		hash_ctl.hcxt = CurTransactionContext;
 		pendingNotifies->uniqueChannelHash =
-			hash_create("Pending Notify Channel Names",
-						64L,
-						&hash_ctl,
-						HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+			hash_make_cxt(ChannelName, channel, "Pending Notify Channel Names",
+						  64L,
+						  CurTransactionContext);
 
index 5b86a727587..eb1a7ecfebc 100644
 
 /*
index e1b808bbb60..5a2683c0735 100644
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cd6d720386f..c44d567657f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2178,19 +2178,9 @@ ExecuteTruncateGuts(List *explicit_rels,

index 327c27abff9..7a95ca73199 100644
@@ -5349,15 +5349,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
index 0d43d66c1cd..bf0b94d3c72 100644

 
 	if (*p_htable == NULL)
 	{
-		HASHCTL		ctl;
-
 
index b2fbd6a082b..73eb57023a6 100644
@@ -702,19 +702,10 @@ get_relation_notnullatts(PlannerInfo *root, Relation relation)
 
 	/*
index fe15881af4e..1280679ab28 100644
@@ -2118,12 +2118,9 @@ lookup_proof_cache(Oid pred_op, Oid clause_op, bool refute_it)
 
 		/* Arrange to flush cache on pg_amop changes */
 		CacheRegisterSyscacheCallback(AMOPOPID,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 91bcda34a37..c6864586962 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -616,19 +616,12 @@ static void
 build_join_rel_hash(PlannerInfo *root)
 {
 	HTAB	   *hashtab;
-	HASHCTL		hash_ctl;
 	ListCell   *l;

index a6b402f2d7b..96869d5df5b 100644
 
@@ -1029,12 +1030,9 @@ find_oper_cache_entry(OprCacheKey *key)
 
index c3d275f8726..db26e4a82b6 100644
 
 	pdir = palloc_object(PartitionDirectoryData);
 	pdir->pdir_mcxt = mcxt;

 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 695e187ba11..b655e264388 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -904,7 +904,6 @@ rebuild_database_list(Oid newdb)
 	MemoryContext newcxt;
 	MemoryContext oldcxt;
 	MemoryContext tmpcxt;
-	HASHCTL		hctl;
 	int			score;
 	int			nelems;
 	HTAB	   *dbhash;
@@ -934,12 +933,10 @@ rebuild_database_list(Oid newdb)
 	 * score, and finally put the array elements into the new doubly linked
 	 * list.
 	 */
-	hctl.keysize = sizeof(Oid);
-	hctl.entrysize = sizeof(avl_dbase);
-	hctl.hcxt = tmpcxt;
-	dbhash = hash_create("autovacuum db hash", 20, &hctl,	/* magic number here
-															 * FIXME */
-						 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	dbhash = hash_make_cxt(avl_dbase, adl_datid,
+						   "autovacuum db hash",
+						   20,	/* magic number here FIXME */
+						   tmpcxt);
 
 	/* start by inserting the new database */
 	score = 0;
@@ -1881,7 +1878,6 @@ do_autovacuum(void)
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
-	HASHCTL		ctl;
 	HTAB	   *table_toast_map;
 	ListCell   *volatile cell;
 	BufferAccessStrategy bstrategy;
@@ -1954,13 +1950,9 @@ do_autovacuum(void)
 	pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel));

 	/*
 	 * Scan pg_class to determine which tables to vacuum.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3c982c6ffac..a4af47b0866 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -1309,7 +1309,6 @@ CompactCheckpointerRequestQueue(void)
 	int			num_requests;
 	int			read_idx,
 				write_idx;
-	HASHCTL		ctl;
 	HTAB	   *htab;
 	bool	   *skip_slot;
 
@@ -1329,14 +1328,9 @@ CompactCheckpointerRequestQueue(void)
 	head = CheckpointerShmem->head;

 	/*
index d78693ffa8e..507757db7eb 100644
@@ -487,16 +487,9 @@ pa_allocate_worker(TransactionId xid)
 
 	/* Create an entry for the requested transaction. */
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 0b1d80b5b0f..fcf295f1df1 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -105,8 +105,6 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
 static void
 logicalrep_relmap_init(void)
 {
-	HASHCTL		ctl;
-
 	if (!LogicalRepRelMapContext)
 		LogicalRepRelMapContext =
 			AllocSetContextCreate(CacheMemoryContext,
@@ -114,12 +112,9 @@ logicalrep_relmap_init(void)
 								  ALLOCSET_DEFAULT_SIZES);

 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(logicalrep_relmap_invalidate_cb,
@@ -611,8 +606,6 @@ logicalrep_partmap_reset_relmap(LogicalRepRelation *remoterel)
 static void
 logicalrep_partmap_init(void)
 {
-	HASHCTL		ctl;
-
 	if (!LogicalRepPartMapContext)
 		LogicalRepPartMapContext =
 			AllocSetContextCreate(CacheMemoryContext,
@@ -620,12 +613,9 @@ logicalrep_partmap_init(void)
 								  ALLOCSET_DEFAULT_SIZES);

 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(logicalrep_partmap_invalidate_cb,
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 4c230bcc8e4..f8b8f8c62c6 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -325,7 +325,6 @@ ReorderBuffer *
 ReorderBufferAllocate(void)
 {
 	ReorderBuffer *buffer;
-	HASHCTL		hash_ctl;
 	MemoryContext new_ctx;
 
 	Assert(MyReplicationSlot != NULL);
@@ -338,8 +337,6 @@ ReorderBufferAllocate(void)
 	buffer =
 		(ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));

 	buffer->change_context = SlabContextCreate(new_ctx,
@@ -368,12 +365,8 @@ ReorderBufferAllocate(void)
 												  SLAB_DEFAULT_BLOCK_SIZE,
 												  SLAB_DEFAULT_BLOCK_SIZE);

 	buffer->by_txn_last_xid = InvalidTransactionId;
 	buffer->by_txn_last_txn = NULL;
@@ -1837,22 +1830,17 @@ static void
 ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
 	dlist_iter	iter;
-	HASHCTL		hash_ctl;

 
 	dlist_foreach(iter, &txn->tuplecids)
 	{
@@ -4976,15 +4964,10 @@ StartupReorderBuffer(void)
 static void
 ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASHCTL		hash_ctl;
-
 	Assert(txn->toast_hash == NULL);

 /*
index f49a4852ecb..a83e509f200 100644
@@ -118,6 +118,7 @@
 
 	/*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 857ebf7d6fb..132b52e3611 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1974,7 +1974,6 @@ pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
 static void
 init_rel_sync_cache(MemoryContext cachectx)
 {
-	HASHCTL		ctl;
 	static bool relation_callbacks_registered = false;
 
 	/* Nothing to do if hash table already exists */
@@ -1982,13 +1981,9 @@ init_rel_sync_cache(MemoryContext cachectx)
 		return;

 
index 23d85fd32e2..95653944a9d 100644

 
 /*
index 404c6bccbdd..7f5f1fb0aa2 100644
 
 	/*

 	if (!LocalBufHash)
index 25fa2151309..26e810e6b50 100644
 
 		/*
 
index 55e4a5421de..08349803e26 100644
@@ -273,8 +273,6 @@ ShmemAddrIsValid(const void *addr)
@@ -283,13 +281,8 @@ InitShmemIndex(void)
 
 /*
@@ -358,6 +351,28 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 	return hash_create(name, init_size, infoP, hash_flags);
 }
 
+/*
index f3ad90c7c7a..1c41d51f9d0 100644
@@ -96,7 +97,6 @@ void

@@ -104,18 +104,12 @@ InitRecoveryTransactionEnvironment(void)
 
 	/*
index d930c66cdbd..91911aa8a2d 100644

 	/*

@@ -3394,20 +3380,13 @@ CheckForSessionAndXactLocks(void)
 		bool		xactLock;	/* is any lockmode held at xact level? */
 	} PerLockTagEntry;
 
-	HASHCTL		hash_ctl;
 	HTAB	   *lockhtab;
 	HASH_SEQ_STATUS status;
 	LOCALLOCK  *locallock;

 	/* Scan local lock table to find entries for each LOCKTAG */
 	hash_seq_init(&status, LockMethodLocalHash);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 49382de88fc..77c7712cc60 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -286,7 +286,6 @@ static lwlock_stats * get_lwlock_stats_entry(LWLock *lock);
 static void
 init_lwlock_stats(void)
 {
-	HASHCTL		ctl;
 	static MemoryContext lwlock_stats_cxt = NULL;
 	static bool exit_registered = false;
 
@@ -306,11 +305,8 @@ init_lwlock_stats(void)
 											 ALLOCSET_DEFAULT_SIZES);
 	MemoryContextAllowInCriticalSection(lwlock_stats_cxt, true);
 
index edabbf4ca31..316ca2c2692 100644
 #include "utils/wait_event.h"
@@ -1155,7 +1156,7 @@ CheckPointPredicate(void)
@@ -1174,16 +1175,13 @@ PredicateLockShmemInit(void)
 
 	/*
@@ -1206,20 +1204,17 @@ PredicateLockShmemInit(void)

 	/*
@@ -1293,15 +1288,12 @@ PredicateLockShmemInit(void)
 
 	/*
@@ -1948,16 +1940,12 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
 
 /*
index 5391640d861..e48b383b466 100644

 /*
 
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 2c964b6f3d9..b8f3136e31d 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -131,8 +131,6 @@ InitSync(void)
 	 */
 	if (!IsUnderPostmaster || AmCheckpointerProcess())
 	{
-		HASHCTL		hash_ctl;
-
 		/*
 		 * XXX: The checkpointer needs to add entries to the pending ops table
 		 * when absorbing fsync requests.  That is done within a critical
@@ -147,13 +145,8 @@ InitSync(void)
 											  ALLOCSET_DEFAULT_SIZES);
 		MemoryContextAllowInCriticalSection(pendingOpsCxt, true);
 
index 48ee050e37f..49ec4ab4db5 100644

 
index aca2c8fc742..33610140e4a 100644

 
 
 /*
index 7bb000ddbd3..bdc7e2237f6 100644

 

index 0b161398465..78adcc38a77 100644
 
 static void
index d5b64d7fca5..2230b42ebae 100644

 	state = palloc0_object(JHashState);
 	sem = palloc0_object(JsonSemAction);
@@ -4213,7 +4207,6 @@ populate_recordset_object_start(void *state)
 {
 	PopulateRecordsetState *_state = (PopulateRecordsetState *) state;
 	int			lex_level = _state->lex->lex_level;
-	HASHCTL		ctl;

 
 	return JSON_SUCCESS;
 }
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..4b0fde3afcb 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -186,17 +186,10 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	int			context_id;
 	List	   *contexts;
-	HASHCTL		ctl;
 	HTAB	   *context_id_lookup;

 
index d22b8ef7f3c..b32c5b971d4 100644
@@ -2861,30 +2861,25 @@ ri_NullCheck(TupleDesc tupDesc,

index 6298a37f88e..9fb4ad78e57 100644
@@ -3888,7 +3888,6 @@ static void
@@ -3904,13 +3903,9 @@ set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 
 	/* Preload the hash table with names appearing in parent_namespaces */
 	foreach(lc, parent_namespaces)
@@ -4981,7 +4976,6 @@ expand_colnames_array_to(deparse_columns *colinfo, int n)
 static void
 build_colinfo_names_hash(deparse_columns *colinfo)
 {
-	HASHCTL		hash_ctl;
 	int			i;
 	ListCell   *lc;
 
@@ -4997,13 +4991,10 @@ build_colinfo_names_hash(deparse_columns *colinfo)
 
 	/*
index 9244a23013e..41712180b1c 100644
 
@@ -97,22 +98,15 @@ relatt_cache_syshash(const void *key, Size keysize)
 static void
 
 	/* Make sure we've initialized CacheMemoryContext. */
 	if (!CacheMemoryContext)
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 3fe89c9c98f..b5b72a6a47e 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -77,7 +77,6 @@ EventCacheLookup(EventTriggerEvent event)
 static void
 BuildEventTriggerCache(void)
 {
-	HASHCTL		ctl;
 	HTAB	   *cache;
 	Relation	rel;
 	Relation	irel;
@@ -114,11 +113,9 @@ BuildEventTriggerCache(void)
 	EventTriggerCacheState = ETCS_REBUILD_STARTED;

 	/*
index 701c294b88d..bef938c37c0 100644

 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a1c88c6b1b6..793b2e6cbd3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1672,17 +1672,14 @@ LookupOpclassInfo(Oid operatorClassOid,

 
 	opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
@@ -3996,7 +3993,6 @@ RelationAssumeNewRelfilelocator(Relation relation)
 void
 RelationCacheInitialize(void)
 {
-	HASHCTL		ctl;
 	int			allocsize;
 
 	/*
@@ -4008,10 +4004,9 @@ RelationCacheInitialize(void)
 	/*
 
 	/*
index 6f970fafa05..09b05dec9d0 100644

index 362169b7d97..acff62a38be 100644
@@ -78,14 +79,10 @@ InvalidateTableSpaceCacheCallback(Datum arg, SysCacheIdentifier cacheid,
 
index 744c8e71d71..6361193aef9 100644
index cebe7a916fb..5e6650404f4 100644
@@ -395,28 +395,23 @@ lookup_type_cache(Oid type_id, int flags)

 
 
@@ -2076,15 +2071,11 @@ assign_record_type_typmod(TupleDesc tupDesc)
 
index e636cc81cf8..5d99467e2bd 100644

index 4e26df7c63a..7c50d38a981 100644
 
 /*
 
index d77502838c4..268b5f35e09 100644

 
index c06b0e9b800..a6cb46402aa 100644
 
index c1a53e658cb..ed614cf5c27 100644

 
 /*
index 614b7c1006b..4c31675535a 100644

 	/*
index 02eced3b2c5..49044bc808b 100644
 
 	if (inited)
 		return;
 
 	/*
 
 	/*
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 84552e32c87..cb12dba9fd5 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3981,8 +3981,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 EState *simple_eval_estate,
 					 ResourceOwner simple_eval_resowner)
 {
-	HASHCTL		ctl;
-
 	/* this link will be restored at exit from plpgsql_call_handler */
 	func->cur_estate = estate;
 
@@ -4037,12 +4035,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 
@@ -4050,13 +4046,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
@@ -4064,12 +4056,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
index 72806c17e17..71a76b85ec8 100644

@@ -145,7 +146,6 @@ static void
 PLy_add_exceptions(PyObject *plpy)
 {
 	PyObject   *excmod;
-	HASHCTL		hash_ctl;
 
 	PLy_exc_error = PLy_create_exception("plpy.Error", NULL, NULL,
 										 "Error", plpy);
@@ -158,10 +158,9 @@ PLy_add_exceptions(PyObject *plpy)
 	if (excmod == NULL)
 		PLy_elog(ERROR, "could not create the spiexceptions module");

 
index 750ba586e0c..fc728f7ab8a 100644
 
 /*
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index b7318f7261e..76d15f1538e 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -408,7 +408,6 @@ void
 _PG_init(void)
 {
 	Tcl_NotifierProcs notifier;
-	HASHCTL		hash_ctl;

 
index eac988c21e7..c68de93b535 100644

-- 
2.53.0

From b4bb9a2e726cc17cccee5866905af2bccbf237b3 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:09 +0100
Subject: [PATCH v11 4/5] Use foreach_hash macro throughout the codebase

At the very least we should choose a few places where we use the new
macros to make sure they have coverage.
---
 contrib/dblink/dblink.c                       |  5 +-
 .../pg_stat_statements/pg_stat_statements.c   | 39 +++------
 contrib/pg_trgm/trgm_regexp.c                 | 18 +---
 contrib/postgres_fdw/connection.c             | 26 ++----
 contrib/postgres_fdw/shippable.c              |  6 +-
 src/backend/access/heap/rewriteheap.c         | 18 +---
 src/backend/access/transam/xlogutils.c        | 20 +----
 src/backend/catalog/pg_enum.c                 | 16 ++--
 src/backend/catalog/storage.c                 | 18 ++--
 src/backend/commands/async.c                  | 21 ++---
 src/backend/commands/prepare.c                | 12 +--
 src/backend/commands/tablecmds.c              |  7 +-
 src/backend/optimizer/util/predtest.c         |  7 +-
 src/backend/parser/parse_oper.c               |  7 +-
 src/backend/partitioning/partdesc.c           |  6 +-
 src/backend/postmaster/autovacuum.c           |  7 +-
 src/backend/replication/logical/relation.c    | 37 ++------
 .../replication/logical/reorderbuffer.c       | 12 +--
 src/backend/replication/pgoutput/pgoutput.c   | 24 ++---
 src/backend/storage/ipc/shmem.c               | 12 +--
 src/backend/storage/ipc/standby.c             | 12 +--
 src/backend/storage/lmgr/lock.c               | 67 +++-----------
 src/backend/storage/lmgr/lwlock.c             |  7 +-
 src/backend/storage/lmgr/predicate.c          | 18 +---
 src/backend/storage/smgr/smgr.c               |  7 +-
 src/backend/storage/sync/sync.c               | 17 ++--
 src/backend/tsearch/ts_typanalyze.c           | 11 +--
 src/backend/utils/activity/wait_event.c       |  6 +-
 src/backend/utils/adt/array_typanalyze.c      | 17 ++--
 src/backend/utils/cache/relcache.c            | 67 +++++---------
 src/backend/utils/cache/relfilenumbermap.c    |  6 +-
 src/backend/utils/cache/spccache.c            |  6 +-
 src/backend/utils/cache/ts_cache.c            |  5 +-
 src/backend/utils/cache/typcache.c            | 14 +--
 src/backend/utils/misc/guc.c                  | 30 ++-----
 src/backend/utils/mmgr/portalmem.c            | 87 ++++---------------
 src/pl/plperl/plperl.c                        |  6 +-
 37 files changed, 154 insertions(+), 547 deletions(-)

index 36d4c3a1847..466948a659b 100644
 
 	if (remoteConnHash)
 	{
index 8a67390e561..9193d3d18c3 100644

 	/*

 

index 383ce6b31b1..26403f8b02a 100644
@@ -1452,10 +1452,8 @@ prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2)
@@ -1466,8 +1464,7 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
 
 	i = 0;
 
@@ -1929,8 +1926,6 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 	int			snumber = 2,
 				arcIndex,
 				arcsCount;
-	HASH_SEQ_STATUS scan_status;
-	TrgmState  *state;
 	TrgmPackArcInfo *arcs;
 	TrgmPackedArc *packedArcs;
 	TrgmPackedGraph *result;
@@ -1938,8 +1933,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 				j;
 
@@ -1961,8 +1955,7 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
@@ -2201,16 +2194,13 @@ static void
 
 	initStringInfo(&buf);

 
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 975394fa3b1..dc3a5e58783 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1065,8 +1065,6 @@ pgfdw_report_internal(int elevel, PGresult *res, PGconn *conn,
 static void
 pgfdw_xact_callback(XactEvent event, void *arg)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 	List	   *pending_entries = NIL;
 	List	   *cancel_requested = NIL;
 
@@ -1078,8 +1076,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 
@@ -1216,8 +1213,6 @@ static void
@@ -1236,8 +1231,7 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 
@@ -1328,14 +1322,10 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 static void
 pgfdw_inval_callback(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
-
 	Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
 
@@ -2186,8 +2176,6 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,

@@ -2210,8 +2198,7 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 			elog(ERROR, "incorrect number of output arguments");
 	}
 
@@ -2463,8 +2450,6 @@ postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
 
@@ -2475,8 +2460,7 @@ disconnect_cached_connections(Oid serverid)
 	if (!ConnectionHash)
 		return false;
 
index cba1c0967a9..014c537115a 100644
@@ -66,17 +66,13 @@ static void
 InvalidateShippableCacheCallback(Datum arg, SysCacheIdentifier cacheid,
 								 uint32 hashvalue)
index 33be08de362..8e27465b0b7 100644
@@ -291,16 +291,11 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
@@ -798,8 +793,6 @@ logical_begin_heap_rewrite(RewriteState state)
 
 	Assert(state->rs_logical_rewrite);
@@ -811,8 +804,7 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
 	elog(DEBUG1, "flushing %u logical rewrite mapping entries",
 		 state->rs_num_rewrite_mappings);
 
@@ -896,9 +888,6 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
@@ -908,8 +897,7 @@ logical_end_heap_rewrite(RewriteState state)
 
index d11e42c9490..3c7fc65b8d4 100644

 
 
index 86c8bada557..be4a8ecd9e6 100644
 
@@ -852,12 +850,10 @@ SerializeUncommittedEnums(void *space, Size size)
 
index db3e08319b5..27d21b556be 100644
 
 	if (!pendingSyncHash)

 
 
 	hash_destroy(tmphash);

 
 
-	hash_seq_init(&scan, pendingSyncHash);
-	while ((pendingsync = (PendingRelSync *) hash_seq_search(&scan)))
+	foreach_hash(PendingRelSync, pendingsync, pendingSyncHash)
 	{
 		ForkNumber	fork;
 		BlockNumber nblocks[MAX_FORKNUM + 1];
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 0c21f528498..1e16afa0bab 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -1229,14 +1229,10 @@ PreCommit_Notify(void)
 		pendingNotifies->uniqueChannelNames = NIL;
 		if (pendingNotifies->uniqueChannelHash != NULL)
 		{
-			HASH_SEQ_STATUS status;
-			ChannelName *channelEntry;
-
-			hash_seq_init(&status, pendingNotifies->uniqueChannelHash);
-			while ((channelEntry = (ChannelName *) hash_seq_search(&status)) != NULL)
+			foreach_hash(ChannelName, channelEntry, pendingNotifies->uniqueChannelHash)
 				pendingNotifies->uniqueChannelNames =
-					lappend(pendingNotifies->uniqueChannelNames,
-							channelEntry->channel);
+				lappend(pendingNotifies->uniqueChannelNames,
+						channelEntry->channel);
 		}
 		else
 		{
@@ -1653,8 +1649,6 @@ PrepareTableEntriesForUnlisten(const char *channel)
 static void
 PrepareTableEntriesForUnlistenAll(void)
 {
-	HASH_SEQ_STATUS seq;
-	ChannelName *channelEntry;
 	PendingListenEntry *pending;
 
 	/*
@@ -1662,8 +1656,7 @@ PrepareTableEntriesForUnlistenAll(void)
 	 * we are listening on or have prepared to listen on.  Record an UNLISTEN
 	 * action for each one, overwriting any earlier attempt to LISTEN.
 	 */
-	hash_seq_init(&seq, localChannelTable);
-	while ((channelEntry = (ChannelName *) hash_seq_search(&seq)) != NULL)
+	foreach_hash(ChannelName, channelEntry, localChannelTable)
 	{
 		pending = (PendingListenEntry *)
 			hash_search(pendingListenActions, channelEntry->channel, HASH_ENTER, NULL);
@@ -1710,9 +1703,6 @@ RemoveListenerFromChannel(GlobalChannelEntry **entry_ptr,
 static void
 ApplyPendingListenActions(bool isCommit)
 {
-	HASH_SEQ_STATUS seq;
-	PendingListenEntry *pending;
-
 	/* Quick exit if nothing to do */
 	if (pendingListenActions == NULL)
 		return;
@@ -1722,8 +1712,7 @@ ApplyPendingListenActions(bool isCommit)
 		elog(PANIC, "global channel table missing post-commit/abort");
 
 	/* For each staged action ... */
-	hash_seq_init(&seq, pendingListenActions);
-	while ((pending = (PendingListenEntry *) hash_seq_search(&seq)) != NULL)
+	foreach_hash(PendingListenEntry, pending, pendingListenActions)
 	{
 		GlobalChannelKey key;
 		GlobalChannelEntry *entry;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index eb1a7ecfebc..4ad9c10a7f8 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -533,16 +533,12 @@ DropPreparedStatement(const char *stmt_name, bool showError)
 void
 DropAllPreparedStatements(void)
 {
-	HASH_SEQ_STATUS seq;
-	PreparedStatement *entry;
-
 	/* nothing cached */
 	if (!prepared_queries)
 		return;
 
 	/* walk over cache */
@@ -689,11 +685,7 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
index c44d567657f..bba607fe7ab 100644
@@ -2260,14 +2260,9 @@ ExecuteTruncateGuts(List *explicit_rels,
 
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 1280679ab28..0583346d75e 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -2344,15 +2344,10 @@ static void
 InvalidateOprProofCacheCallBack(Datum arg, SysCacheIdentifier cacheid,
 								uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	OprProofCacheEntry *hentry;
-
 	Assert(OprProofCacheHash != NULL);
 
 	/* Currently we just reset all entries; hard to be smarter ... */
-	hash_seq_init(&status, OprProofCacheHash);
-
-	while ((hentry = (OprProofCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(OprProofCacheEntry, hentry, OprProofCacheHash)
 	{
 		hentry->have_implic = false;
 		hentry->have_refute = false;
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 96869d5df5b..20de50b93cc 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -1078,15 +1078,10 @@ static void
 InvalidateOprCacheCallBack(Datum arg, SysCacheIdentifier cacheid,
 						   uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	OprCacheEntry *hentry;
-
 	Assert(OprCacheHash != NULL);
 
index db26e4a82b6..3ecc938723b 100644
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b655e264388..89c37a73b10 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1018,8 +1018,6 @@ rebuild_database_list(Oid newdb)
 		TimestampTz current_time;
 		int			millis_increment;
 		avl_dbase  *dbary;
-		avl_dbase  *db;
-		HASH_SEQ_STATUS seq;
 		int			i;
 
 		/* put all the hash elements into an array */
@@ -1030,8 +1028,7 @@ rebuild_database_list(Oid newdb)
 #endif
 
 		i = 0;
 
 		/* sort the array */
@@ -1056,7 +1053,7 @@ rebuild_database_list(Oid newdb)
 		 */
 		for (i = 0; i < nelems; i++)
 		{
-			db = &(dbary[i]);
+			avl_dbase  *db = &(dbary[i]);
 
 			current_time = TimestampTzPlusMilliseconds(current_time,
 													   millis_increment);
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index fcf295f1df1..fef9c357a3f 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -64,25 +64,19 @@ static Oid	FindLogicalRepLocalIndex(Relation localrel, LogicalRepRelation *remot
 static void
 logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
 {
-	LogicalRepRelMapEntry *entry;
-
 	/* Just to be sure. */
 	if (LogicalRepRelMap == NULL)
 		return;
 
@@ -90,11 +84,7 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
@@ -531,25 +521,19 @@ logicalrep_rel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode)
 
@@ -557,11 +541,7 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
@@ -579,15 +559,12 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)

 
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index f8b8f8c62c6..643a4410fac 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -5249,15 +5249,11 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 static void
 ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASH_SEQ_STATUS hstat;
-	ReorderBufferToastEnt *ent;
-
 	if (txn->toast_hash == NULL)
 		return;

@@ -5320,11 +5316,7 @@ typedef struct RewriteMappingFile
 static void
index 132b52e3611..e19df8d556b 100644
@@ -2377,13 +2377,9 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 
-	hash_seq_init(&hash_seq, RelationSyncCache);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 	{
 		/*
 		 * We can set the schema_sent flag for an entry that has committed xid
@@ -2412,8 +2408,6 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
 static void
 rel_sync_cache_relation_cb(Datum arg, Oid relid)
 {
-	RelationSyncEntry *entry;
-
 	/*
 	 * We can get here if the plugin was used in SQL interface as the
 	 * RelationSyncCache is destroyed when the decoding finishes, but there is
@@ -2436,18 +2430,16 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
 		 * Getting invalidations for relations that aren't in the table is
 		 * entirely normal.  So we don't care if it's found or not.
 		 */
-		entry = (RelationSyncEntry *) hash_search(RelationSyncCache, &relid,
-												  HASH_FIND, NULL);
+		RelationSyncEntry *entry = hash_search(RelationSyncCache, &relid,
+											   HASH_FIND, NULL);
+
 		if (entry != NULL)
 			entry->replicate_valid = false;
 	}
 	else
 	{
 		/* Whole cache must be flushed. */
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, RelationSyncCache);
-		while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 		{
 			entry->replicate_valid = false;
 		}
@@ -2463,9 +2455,6 @@ static void
 rel_sync_cache_publication_cb(Datum arg, SysCacheIdentifier cacheid,
 							  uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	RelationSyncEntry *entry;
-
 	/*
 	 * We can get here if the plugin was used in SQL interface as the
 	 * RelationSyncCache is destroyed when the decoding finishes, but there is
@@ -2478,8 +2467,7 @@ rel_sync_cache_publication_cb(Datum arg, SysCacheIdentifier cacheid,
 	 * We have no easy way to identify which cache entries this invalidation
 	 * event might have affected, so just mark them all invalid.
 	 */
-	hash_seq_init(&status, RelationSyncCache);
-	while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelationSyncEntry, entry, RelationSyncCache)
 	{
 		entry->replicate_valid = false;
 	}
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 08349803e26..fddf5b17d47 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -526,8 +526,6 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 {
 #define PG_GET_SHMEM_SIZES_COLS 4
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hstat;
-	ShmemIndexEnt *ent;
 	Size		named_allocated = 0;
 	Datum		values[PG_GET_SHMEM_SIZES_COLS];
 	bool		nulls[PG_GET_SHMEM_SIZES_COLS];
@@ -536,11 +534,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)

@@ -583,8 +579,6 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
@@ -634,10 +628,8 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)

index 1c41d51f9d0..ed1c141d52f 100644
@@ -1100,13 +1100,9 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
 
@@ -1124,11 +1120,7 @@ StandbyReleaseAllLocks(void)
 
index 91911aa8a2d..21afae2efd7 100644

 	if (locallocks == NULL)
 	{
@@ -3381,17 +3362,13 @@ CheckForSessionAndXactLocks(void)
 	} PerLockTagEntry;
 
 	HTAB	   *lockhtab;

@@ -3454,16 +3431,11 @@ CheckForSessionAndXactLocks(void)
 
 	/* Now do the per-locallock cleanup work */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		TwoPhaseLockRecord record;
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
@@ -3551,8 +3523,6 @@ void
 PostPrepare_Locks(FullTransactionId fxid)
 {
 	PGPROC	   *newproc = TwoPhaseGetDummyProc(fxid, false);
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	LOCK	   *lock;
 	PROCLOCK   *proclock;
 	PROCLOCKTAG proclocktag;
@@ -3574,9 +3544,7 @@ PostPrepare_Locks(FullTransactionId fxid)
 	 * pointing to the same proclock, and we daren't end up with any dangling
 	 * pointers.
 	 */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
 		bool		haveSessionLock;
@@ -3772,8 +3740,6 @@ LockData *
 GetLockStatusData(void)
 {
 	LockData   *data;
-	PROCLOCK   *proclock;
-	HASH_SEQ_STATUS seqstat;
 	int			els;
 	int			el;
 	int			i;
@@ -3909,9 +3875,7 @@ GetLockStatusData(void)
 	}
 
@@ -4149,8 +4113,6 @@ xl_standby_lock *
@@ -4172,10 +4134,9 @@ GetRunningTransactionLocks(int *nlocks)
 
@@ -4183,7 +4144,7 @@ GetRunningTransactionLocks(int *nlocks)
@@ -4278,18 +4239,14 @@ void
 
 	proc = MyProc;

 
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 77c7712cc60..51d927eeb3f 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -317,15 +317,10 @@ init_lwlock_stats(void)
 static void
 print_lwlock_stats(int code, Datum arg)
 {
-	HASH_SEQ_STATUS scan;
-	lwlock_stats *lwstats;
-
-	hash_seq_init(&scan, lwlock_stats_htab);
-
 	/* Grab an LWLock to keep different backends from mixing reports */
 	LWLockAcquire(&MainLWLockArray[0].lock, LW_EXCLUSIVE);
 
index 316ca2c2692..be8267e3f9f 100644
@@ -1451,8 +1451,6 @@ GetPredicateLockStatusData(void)
 
 	data = palloc_object(PredicateLockData);
 
@@ -1472,11 +1470,9 @@ GetPredicateLockStatusData(void)

@@ -2933,8 +2929,6 @@ exit:
@@ -2990,9 +2984,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer)

@@ -4415,8 +4407,6 @@ CheckForSerializableConflictIn(Relation relation, const ItemPointerData *tid, Bl
 void
 CheckTableForSerializableConflictIn(Relation relation)
 {
-	HASH_SEQ_STATUS seqstat;
-	PREDICATELOCKTARGET *target;
 	Oid			dbId;
 	Oid			heapId;
 	int			i;
@@ -4450,9 +4440,7 @@ CheckTableForSerializableConflictIn(Relation relation)
 	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

index e48b383b466..d1ad8b5e114 100644
 
-	hash_seq_init(&status, SMgrRelationHash);
-
-	while ((reln = (SMgrRelation) hash_seq_search(&status)) != NULL)
+	foreach_hash(SMgrRelationData, reln, SMgrRelationHash)
 	{
 		smgrrelease(reln);
 	}
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index b8f3136e31d..afca80d2cff 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -281,8 +281,6 @@ ProcessSyncRequests(void)
 {
 	static bool sync_in_progress = false;
 
-	HASH_SEQ_STATUS hstat;
 
 	/* Statistics on sync times */
@@ -339,8 +337,7 @@ ProcessSyncRequests(void)
 	if (sync_in_progress)
 	{
 		/* prior try failed, so update any stale cycle_ctr values */
-		hash_seq_init(&hstat, pendingOps);
-		while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
+		foreach_hash(PendingFsyncEntry, entry, pendingOps)
 		{
 			entry->cycle_ctr = sync_cycle_ctr;
 		}
@@ -354,8 +351,7 @@ ProcessSyncRequests(void)

@@ -381,8 +377,8 @@ ProcessSyncRequests(void)
 			 * If in checkpointer, we want to absorb pending requests every so
 			 * often to prevent overflow of the fsync request queue.  It is
 			 * unspecified whether newly-added entries will be visited by
-			 * hash_seq_search, but we don't care since we don't need to
-			 * process them anyway.
+			 * foreach_hash, but we don't care since we don't need to process
+			 * them anyway.
 			 */
 			if (--absorb_counter <= 0)
 			{
@@ -496,13 +492,10 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
 	}
 	else if (type == SYNC_FILTER_REQUEST)
 	{
-		HASH_SEQ_STATUS hstat;
-		PendingFsyncEntry *pfe;
 		ListCell   *cell;
 
index 49ec4ab4db5..1d04f5fd310 100644

 		sort_table = palloc_array(TrackItem *, i);
 
index 33610140e4a..05179e31818 100644
 
@@ -313,10 +311,8 @@ GetWaitEventCustomNames(uint32 classId, int *nwaitevents)
 	waiteventnames = palloc_array(char *, els);
 
index bdc7e2237f6..281f6800310 100644

 

 
 
 			/* No null element processing other than flag setting here */
 			if (elem_nulls[j])
@@ -458,10 +457,9 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		i = hash_get_num_entries(elements_tab); /* surely enough space */
 		sort_table = palloc_array(TrackItem *, i);
 
-		hash_seq_init(&scan_status, elements_tab);
 		track_len = 0;
 		maxfreq = 0;
-		while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+		foreach_hash(TrackItem, item, elements_tab)
 		{
 			if (item->frequency > cutoff_freq)
 			{
@@ -594,9 +592,8 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			 * increasing count order.
 			 */
 			sorted_count_items = palloc_array(DECountItem *, count_items_count);
-			hash_seq_init(&scan_status, count_tab);
 			j = 0;
-			while ((count_item = (DECountItem *) hash_seq_search(&scan_status)) != NULL)
+			foreach_hash(DECountItem, count_item, count_tab)
 			{
 				sorted_count_items[j++] = count_item;
 			}
@@ -683,11 +680,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 static void
 prune_element_hashtable(HTAB *elements_tab, int b_current)
 {
-	HASH_SEQ_STATUS scan_status;
-	TrackItem  *item;
-
-	hash_seq_init(&scan_status, elements_tab);
-	while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL)
+	foreach_hash(TrackItem, item, elements_tab)
 	{
 		if (item->frequency + item->delta <= b_current)
 		{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 793b2e6cbd3..72089717be0 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -175,7 +175,7 @@ static int	in_progress_list_maxlen;
  * eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
  * cleanup work.  This list intentionally has limited size; if it overflows,
  * we fall back to scanning the whole hashtable.  There is no value in a very
- * large list because (1) at some point, a hash_seq_search scan is faster than
+ * large list because (1) at some point, a foreach_hash scan is faster than
  * retail lookups, and (2) the value of this is to reduce EOXact work for
  * short transactions, which can't have dirtied all that many tables anyway.
  * EOXactListAdd() does not bother to prevent duplicate list entries, so the
@@ -2962,13 +2962,13 @@ RelationCacheInvalidateEntry(Oid relationId)
  *
  *	 We do this in two phases: the first pass deletes deletable items, and
  *	 the second one rebuilds the rebuildable items.  This is essential for
- *	 safety, because hash_seq_search only copes with concurrent deletion of
+ *	 safety, because foreach_hash only copes with concurrent deletion of
  *	 the element it is currently visiting.  If a second SI overflow were to
  *	 occur while we are walking the table, resulting in recursive entry to
  *	 this routine, we could crash because the inner invocation blows away
  *	 the entry next to be visited by the outer scan.  But this way is OK,
  *	 because (a) during the first pass we won't process any more SI messages,
- *	 so hash_seq_search will complete safely; (b) during the second pass we
+ *	 so foreach_hash will complete safely; (b) during the second pass we
  *	 only hold onto pointers to nondeletable entries.
  *
  *	 The two-phase approach also makes it easy to update relfilenumbers for
@@ -2985,8 +2985,6 @@ RelationCacheInvalidateEntry(Oid relationId)
 void
 RelationCacheInvalidate(bool debug_discard)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	Relation	relation;
 	List	   *rebuildFirstList = NIL;
 	List	   *rebuildList = NIL;
@@ -2999,9 +2997,7 @@ RelationCacheInvalidate(bool debug_discard)
 	RelationMapInvalidateAll();
 
 	/* Phase 1 */
 
@@ -3146,12 +3142,9 @@ AssertPendingSyncConsistency(Relation relation)
 void
 AssertPendingSyncs_RelationCache(void)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	Relation   *rels;
 	int			maxrels;
 	int			nrels;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3165,8 +3158,7 @@ AssertPendingSyncs_RelationCache(void)
 	maxrels = 1;
 	rels = palloc(maxrels * sizeof(*rels));
 	nrels = 0;
-	hash_seq_init(&status, GetLockMethodLocalHash());
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, GetLockMethodLocalHash())
 	{
 		Oid			relid;
 		Relation	r;
@@ -3188,8 +3180,7 @@ AssertPendingSyncs_RelationCache(void)
 		rels[nrels++] = r;
 	}

@@ -3217,8 +3208,6 @@ AssertPendingSyncs_RelationCache(void)
 
 	/*
@@ -3230,7 +3219,7 @@ AtEOXact_RelationCache(bool isCommit)
 
 	/*
 	 * Unless the eoxact_list[] overflowed, we only need to examine the rels
-	 * listed in it.  Otherwise fall back on a hash_seq_search scan.
+	 * listed in it.  Otherwise fall back on a foreach_hash scan.
 	 *
 	 * For simplicity, eoxact_list[] entries are not deleted till end of
 	 * top-level transaction, even though we could remove them at
@@ -3241,8 +3230,7 @@ AtEOXact_RelationCache(bool isCommit)
 	 */
 	if (eoxact_list_overflowed)
 	{
-		hash_seq_init(&status, RelationIdCache);
-		while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 		{
 			AtEOXact_cleanup(idhentry->reldesc, isCommit);
 		}
@@ -3251,10 +3239,11 @@ AtEOXact_RelationCache(bool isCommit)
 	{
 		for (i = 0; i < eoxact_list_len; i++)
 		{
-			idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
-													 &eoxact_list[i],
-													 HASH_FIND,
-													 NULL);
+			RelIdCacheEnt *idhentry = hash_search(RelationIdCache,
+												  &eoxact_list[i],
+												  HASH_FIND,
+												  NULL);
+
 			if (idhentry != NULL)
 				AtEOXact_cleanup(idhentry->reldesc, isCommit);
 		}
@@ -3370,8 +3359,6 @@ void
 AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 						  SubTransactionId parentSubid)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3384,13 +3371,12 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 
 	/*
 	 * Unless the eoxact_list[] overflowed, we only need to examine the rels
-	 * listed in it.  Otherwise fall back on a hash_seq_search scan.  Same
-	 * logic as in AtEOXact_RelationCache.
+	 * listed in it.  Otherwise fall back on a foreach_hash scan.  Same logic
+	 * as in AtEOXact_RelationCache.
 	 */
 	if (eoxact_list_overflowed)
 	{
-		hash_seq_init(&status, RelationIdCache);
-		while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+		foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 		{
 			AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
 								mySubid, parentSubid);
@@ -3400,6 +3386,8 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 	{
 		for (i = 0; i < eoxact_list_len; i++)
 		{
+			RelIdCacheEnt *idhentry;
+
 			idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
 													 &eoxact_list[i],
 													 HASH_FIND,
@@ -4096,8 +4084,6 @@ RelationCacheInitializePhase2(void)
 void
 RelationCacheInitializePhase3(void)
 {
-	HASH_SEQ_STATUS status;
-	RelIdCacheEnt *idhentry;
 	MemoryContext oldcxt;
 	bool		needNewCacheFile = !criticalSharedRelcachesBuilt;
 
@@ -4228,15 +4214,13 @@ RelationCacheInitializePhase3(void)
 	 *
 	 * Whenever we access the catalogs to read data, there is a possibility of
 	 * a shared-inval cache flush causing relcache entries to be removed.
-	 * Since hash_seq_search only guarantees to still work after the *current*
+	 * Since foreach_hash only guarantees to still work after the *current*
 	 * entry is removed, it's unsafe to continue the hashtable scan afterward.
 	 * We handle this by restarting the scan from scratch after each access.
 	 * This is theoretically O(N^2), but the number of entries that actually
 	 * need to be fixed is small enough that it doesn't matter.
 	 */
-	hash_seq_init(&status, RelationIdCache);
-
-	while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelIdCacheEnt, idhentry, RelationIdCache)
 	{
 		Relation	relation = idhentry->reldesc;
 		bool		restart = false;
@@ -4346,10 +4330,7 @@ RelationCacheInitializePhase3(void)
 
 		/* Now, restart the hashtable scan if needed */
 		if (restart)
-		{
-			hash_seq_term(&status);
-			hash_seq_init(&status, RelationIdCache);
-		}
+			foreach_hash_restart(idhentry, RelationIdCache);
 	}
 
 	/*
@@ -6601,8 +6582,6 @@ write_relcache_init_file(bool shared)
 
 	/*
@@ -6662,9 +6641,7 @@ write_relcache_init_file(bool shared)
 	/*
index 09b05dec9d0..00df7db4811 100644
 
-	hash_seq_init(&status, RelfilenumberMapHash);
-	while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(RelfilenumberMapEntry, entry, RelfilenumberMapHash)
 	{
 		/*
 		 * If relid is InvalidOid, signaling a complete reset, we must remove
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index acff62a38be..088c0f69cca 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -56,11 +56,7 @@ static void
 InvalidateTableSpaceCacheCallback(Datum arg, SysCacheIdentifier cacheid,
 								  uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	TableSpaceCacheEntry *spc;
-
-	hash_seq_init(&status, TableSpaceCacheHash);
-	while ((spc = (TableSpaceCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(TableSpaceCacheEntry, spc, TableSpaceCacheHash)
 	{
 		if (spc->opts)
 			pfree(spc->opts);
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 6361193aef9..1cddbd98d7b 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -94,11 +94,8 @@ static void
 InvalidateTSCacheCallBack(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
 {
 	HTAB	   *hash = (HTAB *) DatumGetPointer(arg);
-	HASH_SEQ_STATUS status;
-	TSAnyCacheEntry *entry;

 	/* Also invalidate the current-config cache if it's pg_ts_config */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 5e6650404f4..666d42bad4a 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2435,14 +2435,13 @@ InvalidateCompositeTypeCacheEntry(TypeCacheEntry *typentry)
 static void
 TypeCacheRelCallback(Datum arg, Oid relid)
 {
-	TypeCacheEntry *typentry;
-
 	/*
 	 * RelIdToTypeIdCacheHash and TypeCacheHash should exist, otherwise this
 	 * callback wouldn't be registered
 	 */
 	if (OidIsValid(relid))
 	{
+		TypeCacheEntry *typentry;
 		RelIdToTypeIdCacheEntry *relentry;
 
 		/*
@@ -2490,15 +2489,12 @@ TypeCacheRelCallback(Datum arg, Oid relid)
 	}
 	else
 	{
-		HASH_SEQ_STATUS status;
-
 		/*
 		 * Relid is invalid. By convention, we need to reset all composite
 		 * types in cache. Also, we should reset flags for domain types, and
 		 * we loop over all entries in hash, so, do it in a single scan.
 		 */
-		hash_seq_init(&status, TypeCacheHash);
-		while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+		foreach_hash(TypeCacheEntry, typentry, TypeCacheHash)
 		{
 			if (typentry->typtype == TYPTYPE_COMPOSITE)
 			{
@@ -2588,12 +2584,8 @@ TypeCacheTypCallback(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
 static void
 TypeCacheOpcCallback(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	TypeCacheEntry *typentry;
-
 	/* TypeCacheHash must exist, else this callback wouldn't be registered */
-	hash_seq_init(&status, TypeCacheHash);
-	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(TypeCacheEntry, typentry, TypeCacheHash)
 	{
 		bool		hadOpclass = (typentry->flags & TCFLAGS_OPERATOR_FLAGS);
 
index 268b5f35e09..9df13de9012 100644

 

 

 
 	/*
 
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index ed614cf5c27..2479b74a67d 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -33,7 +33,7 @@
  * used in initially sizing the PortalHashTable in EnablePortalManager().
  * Since the hash table can expand, there's no need to make this overly
  * generous, and keeping it small avoids unnecessary overhead in the
- * hash_seq_search() calls executed during transaction end.
+ * foreach_hash loops executed during transaction end.
  */
 #define PORTALS_PER_USER	   16
 
@@ -601,14 +601,10 @@ PortalDrop(Portal portal, bool isTopCommit)
 void
 PortalHashTableDeleteAll(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
 	if (PortalHashTable == NULL)
 		return;
 
-	hash_seq_init(&status, PortalHashTable);
-	while ((hentry = hash_seq_search(&status)) != NULL)
+	foreach_hash(PortalHashEnt, hentry, PortalHashTable)
 	{
 		Portal		portal = hentry->portal;
 
@@ -619,8 +615,7 @@ PortalHashTableDeleteAll(void)
 		PortalDrop(portal, false);
 
 		/* Restart the iteration in case that led to other drops */
-		hash_seq_term(&status);
-		hash_seq_init(&status, PortalHashTable);
+		foreach_hash_restart(hentry, PortalHashTable);
 	}
 }
 
@@ -672,12 +667,8 @@ bool
 PreCommit_Portals(bool isPrepare)
 {
 	bool		result = false;
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);

@@ -759,8 +750,7 @@ PreCommit_Portals(bool isPrepare)
 		 * iteration, because we could have invoked user-defined code that
 		 * caused a drop of the next portal in the hash chain.
 		 */
-		hash_seq_term(&status);
-		hash_seq_init(&status, PortalHashTable);
+		foreach_hash_restart(hentry, PortalHashTable);
 	}
 
 	return result;
@@ -775,12 +765,7 @@ PreCommit_Portals(bool isPrepare)
 void
 AtAbort_Portals(void)
 {
 
@@ -853,12 +838,7 @@ AtAbort_Portals(void)
 void
 AtCleanup_Portals(void)
 {
 
@@ -912,12 +892,7 @@ AtCleanup_Portals(void)
 void
 PortalErrorCleanup(void)
 {
 
@@ -941,12 +916,7 @@ AtSubCommit_Portals(SubTransactionId mySubid,
 					int parentLevel,
 
@@ -977,12 +947,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 
@@ -1087,12 +1052,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 void
 
@@ -1127,8 +1087,6 @@ Datum
 pg_cursor(PG_FUNCTION_ARGS)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hash_seq;
-	PortalHashEnt *hentry;
 
 	/*
 	 * We put all the tuples into a tuplestore in one scan of the hashtable.
@@ -1136,8 +1094,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 	 */
 	InitMaterializedSRF(fcinfo, 0);
 
@@ -1166,12 +1123,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 
@@ -1202,12 +1154,7 @@ ThereAreNoReadyPortals(void)
 void
 HoldPinnedPortals(void)
 {
 
@@ -1251,15 +1198,11 @@ HoldPinnedPortals(void)
 void
 ForgetPortalSnapshots(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
 	int			numPortalSnaps = 0;
 	int			numActiveSnaps = 0;

diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 49044bc808b..2cfdf239ee4 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -503,9 +503,6 @@ set_interp_require(bool trusted)
 static void
 plperl_fini(int code, Datum arg)
 {
-	HASH_SEQ_STATUS hash_seq;
-	plperl_interp_desc *interp_desc;
-
 	elog(DEBUG3, "plperl_fini");
 
 	/*
@@ -527,8 +524,7 @@ plperl_fini(int code, Datum arg)
 	plperl_destroy_interp(&plperl_held_interp);
 
 	/* Zap any fully-initialized interpreters */
-	hash_seq_init(&hash_seq, plperl_interp_hash);
-	while ((interp_desc = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(plperl_interp_desc, interp_desc, plperl_interp_hash)
 	{
 		if (interp_desc->interp)
 		{
-- 
2.53.0

From f552ca455e48d0552212a7ca0300231a703ea8d4 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 3 Dec 2025 23:33:18 +0100
Subject: [PATCH v11 5/5] Inline functions that have now become trivial

---

index 4ad9c10a7f8..ad2f2ab13ec 100644

-
-/*

index 5a2683c0735..74f5aa49d54 100644
 
-/*

 
index bef938c37c0..0e5bfa2a56a 100644

-/*

2.53.0

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Wed, 18 Mar 2026 11:52:02 +0100

On Sat, 14 Mar 2026 at 14:48, Jelte Fennema-Nio <[email-masked]> wrote:
> Newly attached version that fixes a rebase conclict.

v12 attached builds on top of the patch I suggested in[1] (that patch is
icluded here as 0000). Also I added some tests for the new macros to the
test_cplusplusext test module.

[1] https://www.postgresql.org/message-id/[email-masked]
From 53faf1b66901915915423388c0955c9db46e78fd Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 18 Mar 2026 09:37:32 +0100
Subject: [PATCH v12 0/5] Replace __builtin_types_compatible_p with _Generic

Remove all our use of GCC __builtin_types_compatible_p builtin with a
new pg_expr_has_type_p macro that relies on standard C11/C++11 features.
This allows our existing type check macros to work on Visual Studio too.

Some explicit tests for the new macro are added, both for C and C++ to
ensure they behave the same.
---
 config/c-compiler.m4                          | 19 ----------
 configure                                     | 31 +---------------
 configure.ac                                  |  1 -
 meson.build                                   | 15 --------
 src/include/c.h                               | 37 ++++++++++++++-----
 src/include/pg_config.h.in                    |  3 --
 .../test_cplusplusext/test_cplusplusext.cpp   | 17 +++++++++
 src/test/modules/test_extensions/test_ext.c   | 16 ++++++++
 8 files changed, 62 insertions(+), 77 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 629572ee350..d2d93002985 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -267,25 +267,6 @@ fi])# PGAC_CXX_TYPEOF_UNQUAL

-# PGAC_C_TYPES_COMPATIBLE
-# -----------------------
-# Check if the C compiler understands __builtin_types_compatible_p,
-# and define HAVE__BUILTIN_TYPES_COMPATIBLE_P if so.
-#
-# We check usage with __typeof__, though it's unlikely any compiler would
-# have the former and not the latter.
-AC_DEFUN([PGAC_C_TYPES_COMPATIBLE],
-[AC_CACHE_CHECK(for __builtin_types_compatible_p, pgac_cv__types_compatible,
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
-[[ int x; static int y[__builtin_types_compatible_p(__typeof__(x), int)]; ]])],
-[pgac_cv__types_compatible=yes],
-[pgac_cv__types_compatible=no])])
-if test x"$pgac_cv__types_compatible" = xyes ; then
-AC_DEFINE(HAVE__BUILTIN_TYPES_COMPATIBLE_P, 1,
-          [Define to 1 if your compiler understands __builtin_types_compatible_p.])
-fi])# PGAC_C_TYPES_COMPATIBLE
-
-
 # PGAC_C_BUILTIN_CONSTANT_P
 # -------------------------
 # Check if the C compiler understands __builtin_constant_p(),
diff --git a/configure b/configure
index 5aec0afa9ab..e4b847e41dc 100755
--- a/configure
+++ b/configure
@@ -15182,36 +15182,7 @@ _ACEOF
 
   fi
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_types_compatible_p" >&5
-$as_echo_n "checking for __builtin_types_compatible_p... " >&6; }
-if ${pgac_cv__types_compatible+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
- int x; static int y[__builtin_types_compatible_p(__typeof__(x), int)];
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  pgac_cv__types_compatible=yes
-else
-  pgac_cv__types_compatible=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__types_compatible" >&5
-$as_echo "$pgac_cv__types_compatible" >&6; }
-if test x"$pgac_cv__types_compatible" = xyes ; then
-
-$as_echo "#define HAVE__BUILTIN_TYPES_COMPATIBLE_P 1" >>confdefs.h
-
-fi
+PGAC_C_TYPES_COMPATIBLE
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_constant_p" >&5
 $as_echo_n "checking for __builtin_constant_p... " >&6; }
 if ${pgac_cv__builtin_constant_p+:} false; then :
diff --git a/configure.ac b/configure.ac
index fead9a6ce99..47f5cd05a82 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1727,7 +1727,6 @@ PGAC_C_TYPEOF
 PGAC_CXX_TYPEOF
 PGAC_C_TYPEOF_UNQUAL
 PGAC_CXX_TYPEOF_UNQUAL
-PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
 PGAC_C_BUILTIN_UNREACHABLE
diff --git a/meson.build b/meson.build
index 46bd6b1468a..ec3e4f05628 100644
--- a/meson.build
+++ b/meson.build
@@ -2060,21 +2060,6 @@ foreach builtin : builtins
 endforeach

-# Check if the C compiler understands __builtin_types_compatible_p,
-# and define HAVE__BUILTIN_TYPES_COMPATIBLE_P if so.
-#
-# We check usage with __typeof__, though it's unlikely any compiler would
-# have the former and not the latter.
-if cc.compiles('''
-    static int x;
-    static int y[__builtin_types_compatible_p(__typeof__(x), int)];
-    ''',
-    name: '__builtin_types_compatible_p',
-    args: test_c_args)
-  cdata.set('HAVE__BUILTIN_TYPES_COMPATIBLE_P', 1)
-endif
-
-
 # Check if the C compiler understands __builtin_$op_overflow(),
 # and define HAVE__BUILTIN_OP_OVERFLOW if so.
 #
diff --git a/src/include/c.h b/src/include/c.h
index 8987a121d6a..b2f253c9088 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -387,6 +387,23 @@ extern "C++"
 #define HAVE_PG_INTEGER_CONSTANT_P
 #endif
 
+/*
+ * pg_expr_has_type_p(expr, type) - Check if an expression has a specific type.
+ *
+ * The C implementation uses _Generic, which applies lvalue conversion to the
+ * controlling expression: arrays decay to pointers, functions decay to function
+ * pointers, and top-level cv-qualifiers are stripped. The C++ implementation
+ * uses std::decay to match this behavior. Note that only top-level qualifiers
+ * are stripped — pointee qualifiers are preserved (e.g. const int * stays
+ * const int *, but int *const becomes int *).
+ */
+#if defined(__cplusplus)
+#define pg_expr_has_type_p(expr, _type) \
+	std::is_same<typename std::decay<decltype(expr)>::type, _type>::value
+#else
+#define pg_expr_has_type_p(expr, type) _Generic((expr), type: 1, default: 0)
+#endif
+
 /*
  * pg_assume(expr) states that we assume `expr` to evaluate to true. In assert
  * enabled builds pg_assume() is turned into an assertion, in optimized builds
@@ -1051,26 +1068,28 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
  * StaticAssertVariableIsOfType() can be used as a declaration.
  * StaticAssertVariableIsOfTypeMacro() is intended for use in macros, eg
  *		#define foo(x) (StaticAssertVariableIsOfTypeMacro(x, int), bar(x))
- *
- * If we don't have __builtin_types_compatible_p, we can still assert that
- * the types have the same size.  This is far from ideal (especially on 32-bit
- * platforms) but it provides at least some coverage.
  */
-#ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
+#if !defined(_MSC_VER) || _MSC_VER >= 1933
 #define StaticAssertVariableIsOfType(varname, typename) \
-	StaticAssertDecl(__builtin_types_compatible_p(typeof(varname), typename), \
+	StaticAssertDecl(pg_expr_has_type_p(varname, typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
-	(StaticAssertExpr(__builtin_types_compatible_p(typeof(varname), typename), \
+	(StaticAssertExpr(pg_expr_has_type_p(varname, typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
-#else							/* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */
+#else							/* _MSC_VER < 1933 */
+/*
+ * This compiler is buggy and fails to compile the previous variant; use a
+ * fallback implementation that asserts that the types have the same size.
+ * This is far from ideal (especially on 32-bit platforms) but it provides at
+ * least some coverage.
+ */
 #define StaticAssertVariableIsOfType(varname, typename) \
 	StaticAssertDecl(sizeof(varname) == sizeof(typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
 	(StaticAssertExpr(sizeof(varname) == sizeof(typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
-#endif							/* HAVE__BUILTIN_TYPES_COMPATIBLE_P */
+#endif							/* _MSC_VER < 1933 */

 /* ----------------------------------------------------------------
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 79379a4d125..2be8e50465f 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -538,9 +538,6 @@
 /* Define to 1 if your compiler understands __builtin_$op_overflow. */
 #undef HAVE__BUILTIN_OP_OVERFLOW
 
-/* Define to 1 if your compiler understands __builtin_types_compatible_p. */
-#undef HAVE__BUILTIN_TYPES_COMPATIBLE_P
-
 /* Define to 1 if your compiler understands __builtin_unreachable. */
 #undef HAVE__BUILTIN_UNREACHABLE
 
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 93cd7dd07f7..df7a592cb70 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -27,6 +27,23 @@ PG_FUNCTION_INFO_V1(test_cplusplus_add);
 
 StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
 
+/* Same tests as in test_ext.c, but compiled with a C++ compiler to verify that
+ * the pg_expr_has_type_p macro works correctly in C++. */
+StaticAssertDecl(pg_expr_has_type_p((int32) 123, int32), "int32 expression should be int32");
+StaticAssertDecl(!pg_expr_has_type_p((int32) 123, int64), "int32 expression should not be int64");
+StaticAssertDecl(pg_expr_has_type_p(((char (*)[10]) nullptr)[0], char *),
+				 "array should decay into pointer");
+StaticAssertDecl(pg_expr_has_type_p((char (*)[10]) nullptr, char (*)[10]),
+				 "pointer to an aray should work if it has the same size");
+StaticAssertDecl(!pg_expr_has_type_p((char (*)[5]) nullptr, char (*)[10]),
+				 "pointer to an aray should not match if it does not have the same size");
+StaticAssertDecl(pg_expr_has_type_p((const int *) nullptr, const int *),
+				 "const pointers of same type should match");
+StaticAssertDecl(!pg_expr_has_type_p((const int *) nullptr, int *),
+				 "const pointer should not match non-const pointer");
+StaticAssertDecl(pg_expr_has_type_p((const int) 0, int),
+				 "top-level const should be stripped");
+
 /*
  * Simple function that returns the sum of two integers.  This verifies that
  * C++ extension modules can be loaded and called correctly at runtime.
diff --git a/src/test/modules/test_extensions/test_ext.c b/src/test/modules/test_extensions/test_ext.c
index a23165ba67a..d528a770dee 100644
--- a/src/test/modules/test_extensions/test_ext.c
+++ b/src/test/modules/test_extensions/test_ext.c
@@ -13,6 +13,22 @@ PG_MODULE_MAGIC;
 
 PG_FUNCTION_INFO_V1(test_ext);
 
+/* Confirm that C implementation of pg_expr_has_type_p works as expected on all compilers. */
+StaticAssertDecl(pg_expr_has_type_p((int32) 123, int32), "int32 expression should be int32");
+StaticAssertDecl(!pg_expr_has_type_p((int32) 123, int64), "int32 expression should not be int64");
+StaticAssertDecl(pg_expr_has_type_p(((char (*)[10]) NULL)[0], char *),
+				 "array should decay into pointer");
+StaticAssertDecl(pg_expr_has_type_p((char (*)[10]) NULL, char (*)[10]),
+				 "pointer to an aray should work if it has the same size");
+StaticAssertDecl(!pg_expr_has_type_p((char (*)[5]) NULL, char (*)[10]),
+				 "pointer to an aray should not match if it does not have the same size");
+StaticAssertDecl(pg_expr_has_type_p((const int *) NULL, const int *),
+				 "const pointers of same type should match");
+StaticAssertDecl(!pg_expr_has_type_p((const int *) NULL, int *),
+				 "const pointer should not match non-const pointer");
+StaticAssertDecl(pg_expr_has_type_p((const int) 0, int),
+				 "top-level const should be stripped");
+
 Datum
 test_ext(PG_FUNCTION_ARGS)
 {

base-commit: 182cdf5aeaf7b34a288a135d66f2893dc288a24e
-- 
2.53.0

From f3f6d68e42eaea6c22ee71bf4e1e0247b5bb7255 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v12 1/5] Add hash_make macros

Co-Authored-By: Bertrand Drouvot <[email-masked]>
---
 doc/src/sgml/system-views.sgml                |   5 +-
 src/backend/utils/hash/dynahash.c             | 115 ++++++++++++
 src/include/c.h                               |  18 ++
 src/include/storage/shmem.h                   |  32 ++++
 src/include/utils/hsearch.h                   | 166 +++++++++++++++++-
 .../test_cplusplusext/test_cplusplusext.cpp   |  46 +++++
 src/tools/pgindent/typedefs.list              |   1 +
 7 files changed, 377 insertions(+), 6 deletions(-)

 
+/*
index b2f253c9088..86efdc963f1 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -83,6 +83,12 @@
 #ifdef ENABLE_NLS
 #include <libintl.h>
 #endif
+#ifdef __cplusplus
+extern "C++"
+{
+#include <type_traits>
+}
+#endif
 
 #ifdef __cplusplus
 extern "C++"
@@ -404,6 +410,18 @@ extern "C++"
 #define pg_expr_has_type_p(expr, type) _Generic((expr), type: 1, default: 0)
 #endif
 
+/*

+/*

 
+/*
 
-/* Hash table control struct is an opaque type known only within dynahash.c */
-typedef struct HTAB HTAB;
-
-/* Parameter data structure for hash_create */
-/* Only those fields indicated by hash_flags need be set */
+/*
+ * Parameter data structure for hash_create (which is the low-level method of
+ * initializing hash tables, hash_make macros are preferred)
+ * Only those fields indicated by hash_flags need be set
+ */
 typedef struct HASHCTL
 {
 	/* Used if HASH_PARTITION flag is set: */
@@ -131,6 +284,9 @@ typedef struct
  */
 extern HTAB *hash_create(const char *tabname, int64 nelem,
 						 const HASHCTL *info, int flags);
+extern void hash_opts_init(HASHCTL *ctl, int *flags,
+						   Size keysize, Size entrysize, bool string_key,
+						   const HASHOPTS *opts);
 extern void hash_destroy(HTAB *hashp);
 extern void hash_stats(const char *caller, HTAB *hashp);
 extern void *hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action,
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index df7a592cb70..83bb99c4d2d 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -19,6 +19,7 @@ extern "C" {
 #include "fmgr.h"
 #include "nodes/pg_list.h"
 #include "nodes/primnodes.h"
+#include "utils/hsearch.h"
 
 PG_MODULE_MAGIC;
 
@@ -75,6 +76,51 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 	pfree(node);
 	pfree(copy);
 
+	/* Test hash macros compile under C++ */
+	{
+		typedef struct
+		{
+			Oid			oid;
+			int			data;
+		} OidEntry;
+
+		typedef struct
+		{
+			char		name[NAMEDATALEN];
+			int			value;
+		} NameEntry;
+
+		HTAB	   *htab;
+
+		StaticAssertStmt(!HASH_KEY_AS_STRING(OidEntry, oid),
+						 "Oid key should use HASH_BLOBS");
+		StaticAssertStmt(!HASH_TYPE_AS_STRING(Oid),
+						 "Oid hashset should use HASH_BLOBS");
+		StaticAssertStmt(HASH_KEY_AS_STRING(NameEntry, name),
+						 "char[] key should use HASH_STRINGS");
+		StaticAssertStmt(HASH_TYPE_AS_STRING(NameData),
+						 "NameData should use HASH_STRINGS");
+
+		htab = hash_make(OidEntry, oid, "C++ oid hash", 8);
+		hash_destroy(htab);
+
+		htab = hash_make(NameEntry, name, "C++ name hash", 8);
+		hash_destroy(htab);
+
+		htab = hash_make_cxt(OidEntry, oid, "C++ cxt hash", 8,
+							 CurrentMemoryContext);
+		hash_destroy(htab);
+
+		htab = hash_make_ext(OidEntry, oid, "C++ ext hash", 8, NULL);
+		hash_destroy(htab);
+
+		htab = hash_make_fn(OidEntry, oid, "C++ fn hash", 8, NULL, NULL);
+		hash_destroy(htab);
+
+		htab = hashset_make(Oid, "C++ oid hashset", 8);
+		hash_destroy(htab);
+	}
+
 	switch (a)
 	{
 		case 1:
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..727d2dcac79 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1177,6 +1177,7 @@ HASHBUCKET
 HASHCTL
 HASHELEMENT
 HASHHDR
+HASHOPTS
 HASHSEGMENT
 HASH_SEQ_STATUS
 HE
-- 
2.53.0

From fa0fb40bed4e455042e1bb368b9953d415463cfd Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:00 +0100
Subject: [PATCH v12 2/5] Add foreach_hash macro

For lists we've had a new foreach style macros since 14dd0f27d7. This
adds a similar macro for hash tables. This new foreach_hash macro makes
iterating over the items in an HTAB as simple as iterating over the
items in a List. The only additional thing to keep in mind is that when
exiting the loop early you need to call foreach_hash_term.
---
 src/include/utils/hsearch.h                   | 81 ++++++++++++++++++-
 .../test_cplusplusext/test_cplusplusext.cpp   |  4 +
 2 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 83bb99c4d2d..7b281cc3839 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -102,6 +102,10 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 						 "NameData should use HASH_STRINGS");
 
 		htab = hash_make(OidEntry, oid, "C++ oid hash", 8);
+		foreach_hash(OidEntry, myOidEntry, htab)
+		{
+			(void) myOidEntry;
+		}
 		hash_destroy(htab);
 
 		htab = hash_make(NameEntry, name, "C++ name hash", 8);
-- 
2.53.0

From e1b47c3c084c0151bfd3a83983089cc3ed877a9e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:36:19 +0100
Subject: [PATCH v12 3/5] Use hash_make macros throughout the codebase

index ac6127ec1b5..20e8fb84960 100644
@@ -2547,13 +2547,9 @@ getConnectionByName(const char *name)
 
 static remoteConn *
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 6cb14824ec3..d46a4e2ee45 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -518,7 +518,6 @@ static void
 pgss_shmem_startup(void)
 {
 	bool		found;
-	HASHCTL		info;
 	FILE	   *file = NULL;
 	FILE	   *qfile = NULL;
 	uint32		header;
@@ -558,12 +557,9 @@ pgss_shmem_startup(void)
 		pgss->stats.stats_reset = GetCurrentTimestamp();
 	}

 

index 192f8011160..0faf7e69280 100644
@@ -217,17 +217,13 @@ GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state)

 		/*

 	/* Set up invalidation callback on pg_foreign_server. */
 	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index c01a01c5fe7..0315f4bd5b9 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -706,24 +706,17 @@ static HTAB *
 load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
 {
 	HTAB	   *crosstab_hash;
-	HASHCTL		ctl;
 	int			ret;
 	uint64		proc;
 	MemoryContext SPIcontext;

index 46d3fd13a9e..36c9af0b2ac 100644

 static void

 

 
 
 	/*

 

 	/*

 /*
diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index c235eca7c51..df348d5701e 100644
--- a/src/backend/access/transam/xlogprefetcher.c
+++ b/src/backend/access/transam/xlogprefetcher.c
@@ -364,15 +364,13 @@ XLogPrefetcher *
 XLogPrefetcherAllocate(XLogReaderState *reader)
 {
 	XLogPrefetcher *prefetcher;
-	HASHCTL		ctl;

 

 
 /*
 
 /*

 	/*

 

 
 /*

 /*

 
index 876aad2100a..0ab08cceb9c 100644
@@ -373,15 +373,9 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 
 /*
index 551667650ba..af7a29df1f6 100644
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 67e42e5df29..be8f750bf9f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2193,19 +2193,9 @@ ExecuteTruncateGuts(List *explicit_rels,

index 4cd5e262e0f..1ddbfb47620 100644
@@ -5351,15 +5351,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)

 
 	if (*p_htable == NULL)
 	{
-		HASHCTL		ctl;
-

 	/*
index 690a23d619a..0cf77394abc 100644
@@ -2119,12 +2119,9 @@ lookup_proof_cache(Oid pred_op, Oid clause_op, bool refute_it)

 

 

 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 219673db930..b5569623718 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -905,7 +905,6 @@ rebuild_database_list(Oid newdb)
 	MemoryContext newcxt;
 	MemoryContext oldcxt;
 	MemoryContext tmpcxt;
-	HASHCTL		hctl;
 	int			score;
 	int			nelems;
 	HTAB	   *dbhash;
@@ -935,12 +934,10 @@ rebuild_database_list(Oid newdb)
 	 * score, and finally put the array elements into the new doubly linked
 	 * list.
 	 */
-	hctl.keysize = sizeof(Oid);
-	hctl.entrysize = sizeof(avl_dbase);
-	hctl.hcxt = tmpcxt;
-	dbhash = hash_create("autovacuum db hash", 20, &hctl,	/* magic number here
-															 * FIXME */
-						 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	dbhash = hash_make_cxt(avl_dbase, adl_datid,
+						   "autovacuum db hash",
+						   20,	/* magic number here FIXME */
+						   tmpcxt);
 
 	/* start by inserting the new database */
 	score = 0;
@@ -1882,7 +1879,6 @@ do_autovacuum(void)
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
-	HASHCTL		ctl;
 	HTAB	   *table_toast_map;
 	ListCell   *volatile cell;
 	BufferAccessStrategy bstrategy;
@@ -1955,13 +1951,9 @@ do_autovacuum(void)
 	pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel));

 	/*

 
 	/*

 

index 682d13c9f22..9fdb5d4d152 100644

 

 

 	dlist_foreach(iter, &txn->tuplecids)
 	{
@@ -4972,15 +4960,10 @@ StartupReorderBuffer(void)
 static void
 ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASHCTL		hash_ctl;
-
 	Assert(txn->toast_hash == NULL);

 /*
 
 	/*
index 4ecfcbff7ab..6be2ae090cf 100644

 

 
 
 /*
 
 	/*

 	if (!LocalBufHash)
 
 		/*
 
index 0424c445723..64cb5ccda67 100644
@@ -276,8 +276,6 @@ ShmemAddrIsValid(const void *addr)
@@ -286,13 +284,8 @@ InitShmemIndex(void)
 
 /*
@@ -361,6 +354,28 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 	return hash_create(name, init_size, infoP, hash_flags);
 }
 
+/*

 
 	/*

 	/*

-	HASHCTL		hash_ctl;
 	HTAB	   *lockhtab;
 	HASH_SEQ_STATUS status;
 	LOCALLOCK  *locallock;

 

 	/*

 	/*
 
 	/*
 
 /*

 /*

 

 

 
 /*

 

index ab38ddb9079..d24f10c94e6 100644
@@ -934,19 +934,8 @@ json_unique_hash_match(const void *key1, const void *key2, Size keysize)
 
 static void
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index efdf60ca5a5..7eba47bd9c9 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3809,18 +3809,12 @@ static HTAB *
 get_json_object_as_hash(const char *json, int len, const char *funcname,
 						Node *escontext)
 {
-	HASHCTL		ctl;
 	HTAB	   *tab;
 	JHashState *state;
 	JsonSemAction *sem;

@@ -4214,7 +4208,6 @@ populate_recordset_object_start(void *state)
 
 	/* Reject object at top level: we must have an array at level 0 */
 	if (lex_level == 0)
@@ -4228,13 +4221,8 @@ populate_recordset_object_start(void *state)
 		return JSON_SUCCESS;

 	return JSON_SUCCESS;
 }
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 1a4dbbeb8db..4d0c811bdd0 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -188,17 +188,10 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	int			context_id;
 	List	   *contexts;
-	HASHCTL		ctl;
 	HTAB	   *context_id_lookup;

 

index 153a92fd3ea..d300259cf54 100644
@@ -4215,7 +4215,6 @@ static void
@@ -4231,13 +4230,9 @@ set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 
 	/* Preload the hash table with names appearing in parent_namespaces */
 	foreach(lc, parent_namespaces)
@@ -5308,7 +5303,6 @@ expand_colnames_array_to(deparse_columns *colinfo, int n)
 static void
 build_colinfo_names_hash(deparse_columns *colinfo)
 {
-	HASHCTL		hash_ctl;
 	int			i;
 	ListCell   *lc;
 
@@ -5324,13 +5318,10 @@ build_colinfo_names_hash(deparse_columns *colinfo)
 
 	/*

 
 
 	/*

 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3a4f19e8d58..4b98af7e754 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1667,17 +1667,14 @@ LookupOpclassInfo(Oid operatorClassOid,

 
 	opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
@@ -3992,7 +3989,6 @@ RelationAssumeNewRelfilelocator(Relation relation)
 void
 RelationCacheInitialize(void)
 {
-	HASHCTL		ctl;
 	int			allocsize;
 
 	/*
@@ -4004,10 +4000,9 @@ RelationCacheInitialize(void)
 	/*
 
 	/*

 

 

 
index bfeceb7a92f..db8c43d92ce 100644
@@ -33,6 +33,7 @@
 
 /*
@@ -548,14 +549,8 @@ record_C_func(HeapTuple procedureTuple,

 

 	entry = (InjectionPointCacheEntry *)
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 493f9b0ee19..f8a6db5316a 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -105,23 +105,19 @@ static MemoryContext TopPortalContext = NULL;
 void
 EnablePortalManager(void)
 {
-	HASHCTL		ctl;
-
 	Assert(TopPortalContext == NULL);

 
 /*

 	/*
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index c5f11b874c7..15b8cacf57d 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -391,7 +391,6 @@ _PG_init(void)
 	 * "plperl.use_strict"
 	 */
 	static bool inited = false;
-	HASHCTL		hash_ctl;
 
 	if (inited)
 		return;
@@ -461,19 +460,13 @@ _PG_init(void)
 	/*
 	 * Create hash tables.
 	 */
 
 	/*
@@ -579,14 +572,9 @@ select_perl_context(bool trusted)
 
 	/*

 

 

 
 /*
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 44491de669a..f61f605ba9c 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -409,7 +409,6 @@ void
 _PG_init(void)
 {
 	Tcl_NotifierProcs notifier;
-	HASHCTL		hash_ctl;
 
@@ -447,22 +446,16 @@ _PG_init(void)

 
-- 
2.53.0

From f9c024ee00c332e984e1e10379fa364adf2a7277 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:09 +0100
Subject: [PATCH v12 4/5] Use foreach_hash macro throughout the codebase

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 20e8fb84960..4d292bc337b 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -1278,14 +1278,11 @@ PG_FUNCTION_INFO_V1(dblink_get_connections);
 Datum
 dblink_get_connections(PG_FUNCTION_ARGS)
 {
-	HASH_SEQ_STATUS status;
-	remoteConnHashEnt *hentry;
 	ArrayBuildState *astate = NULL;
 
 	if (remoteConnHash)
 	{
index d46a4e2ee45..d52744a2be9 100644
@@ -742,9 +742,7 @@ pgss_shmem_shutdown(int code, Datum arg)
 
@@ -778,8 +776,7 @@ pgss_shmem_shutdown(int code, Datum arg)
@@ -791,8 +788,8 @@ pgss_shmem_shutdown(int code, Datum arg)
@@ -1696,8 +1693,6 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 
 	/*
 	 * Superusers or roles with the privileges of pg_read_all_stats members
@@ -1826,8 +1821,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		}
 	}
 
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		Datum		values[PG_STAT_STATEMENTS_COLS];
 		bool		nulls[PG_STAT_STATEMENTS_COLS];
@@ -2171,9 +2165,7 @@ entry_cmp(const void *lhs, const void *rhs)
 static void
 entry_dealloc(void)
 {
-	HASH_SEQ_STATUS hash_seq;
 	pgssEntry **entries;
-	pgssEntry  *entry;
 	int			nvictims;
 	int			i;
 	Size		tottextlen;
@@ -2197,8 +2189,7 @@ entry_dealloc(void)
 	tottextlen = 0;
 	nvalidtexts = 0;
 
@@ -2510,8 +2501,6 @@ gc_qtexts(void)
 
@@ -2553,8 +2542,7 @@ gc_qtexts(void)
 	extent = 0;
 	nentries = 0;
 
@@ -2577,7 +2565,7 @@ gc_qtexts(void)
 
@@ -2644,8 +2632,7 @@ gc_fail:
@@ -2709,8 +2696,6 @@ if (e) { \
@@ -2730,6 +2715,8 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
@@ -2753,8 +2740,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
@@ -2767,8 +2753,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 
 	i = 0;

 
 	initStringInfo(&buf);

 
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 0faf7e69280..8fba2e23603 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1066,8 +1066,6 @@ pgfdw_report_internal(int elevel, PGresult *res, PGconn *conn,
 static void
 pgfdw_xact_callback(XactEvent event, void *arg)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
 	List	   *pending_entries = NIL;
 	List	   *cancel_requested = NIL;
 
@@ -1079,8 +1077,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 
@@ -1217,8 +1214,6 @@ static void
@@ -1237,8 +1232,7 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 
@@ -1329,14 +1323,10 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 static void
 pgfdw_inval_callback(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
 {
-	HASH_SEQ_STATUS scan;
-	ConnCacheEntry *entry;
-
 	Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
 
@@ -2187,8 +2177,6 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,

@@ -2211,8 +2199,7 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 			elog(ERROR, "incorrect number of output arguments");
 	}
 
@@ -2464,8 +2451,6 @@ postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
 
@@ -2476,8 +2461,7 @@ disconnect_cached_connections(Oid serverid)
 	if (!ConnectionHash)
 		return false;

 

 

 

 	if (!pendingSyncHash)

 
 
 	hash_destroy(tmphash);

 

 	/*
 
index 0ab08cceb9c..968cea8c926 100644
@@ -535,16 +535,12 @@ DropPreparedStatement(const char *stmt_name, bool showError)
 
 	/* walk over cache */
@@ -691,11 +687,7 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
index be8f750bf9f..be83dd1588e 100644
@@ -2275,14 +2275,9 @@ ExecuteTruncateGuts(List *explicit_rels,
 
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 0cf77394abc..d3af50b4fa4 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -2345,15 +2345,10 @@ static void
 InvalidateOprProofCacheCallBack(Datum arg, SysCacheIdentifier cacheid,
 								uint32 hashvalue)
 {
-	HASH_SEQ_STATUS status;
-	OprProofCacheEntry *hentry;
-
 	Assert(OprProofCacheHash != NULL);

 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b5569623718..3d7a1bf318a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1019,8 +1019,6 @@ rebuild_database_list(Oid newdb)
 		TimestampTz current_time;
 		int			millis_increment;
 		avl_dbase  *dbary;
-		avl_dbase  *db;
-		HASH_SEQ_STATUS seq;
 		int			i;
 
 		/* put all the hash elements into an array */
@@ -1031,8 +1029,7 @@ rebuild_database_list(Oid newdb)
 #endif
 
 		i = 0;
 
 		/* sort the array */
@@ -1057,7 +1054,7 @@ rebuild_database_list(Oid newdb)
 		 */
 		for (i = 0; i < nelems; i++)
 		{
-			db = &(dbary[i]);
+			avl_dbase  *db = &(dbary[i]);

 

 
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 9fdb5d4d152..25bd0d8f088 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -5245,15 +5245,11 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 static void
 ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
 {
-	HASH_SEQ_STATUS hstat;
-	ReorderBufferToastEnt *ent;
-
 	if (txn->toast_hash == NULL)
 		return;

@@ -5316,11 +5312,7 @@ typedef struct RewriteMappingFile
 static void
index 6be2ae090cf..9d920fe0af6 100644
 
index 64cb5ccda67..b715f4a11e4 100644
@@ -529,8 +529,6 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
@@ -539,11 +537,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)

@@ -586,8 +582,6 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
@@ -637,10 +631,8 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)

 

 
 	if (locallocks == NULL)
 	{
 
 	HTAB	   *lockhtab;

 

 
 	proc = MyProc;

 

 

 

 

-	HASH_SEQ_STATUS hstat;

 

 

 

 

 

index 4b98af7e754..c0958d035c0 100644
@@ -176,7 +176,7 @@ static int	in_progress_list_maxlen;
@@ -2956,13 +2956,13 @@ RelationCacheInvalidateEntry(Oid relationId)
@@ -2979,8 +2979,6 @@ RelationCacheInvalidateEntry(Oid relationId)
@@ -2993,9 +2991,7 @@ RelationCacheInvalidate(bool debug_discard)
 
 	/* Phase 1 */
 
@@ -3140,12 +3136,9 @@ AssertPendingSyncConsistency(Relation relation)
 void
 AssertPendingSyncs_RelationCache(void)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	Relation   *rels;
 	int			maxrels;
 	int			nrels;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3159,8 +3152,7 @@ AssertPendingSyncs_RelationCache(void)
 	maxrels = 1;
 	rels = palloc(maxrels * sizeof(*rels));
 	nrels = 0;
-	hash_seq_init(&status, GetLockMethodLocalHash());
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, GetLockMethodLocalHash())
 	{
 		Oid			relid;
 		Relation	r;
@@ -3182,8 +3174,7 @@ AssertPendingSyncs_RelationCache(void)
 		rels[nrels++] = r;
 	}

@@ -3211,8 +3202,6 @@ AssertPendingSyncs_RelationCache(void)
 
 	/*
@@ -3224,7 +3213,7 @@ AtEOXact_RelationCache(bool isCommit)
 
 	/*
@@ -3235,8 +3224,7 @@ AtEOXact_RelationCache(bool isCommit)
@@ -3245,10 +3233,11 @@ AtEOXact_RelationCache(bool isCommit)
@@ -3364,8 +3353,6 @@ void
 
 	/*
@@ -3378,13 +3365,12 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 
 	/*
@@ -3394,6 +3380,8 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
@@ -4092,8 +4080,6 @@ RelationCacheInitializePhase2(void)
 
@@ -4224,15 +4210,13 @@ RelationCacheInitializePhase3(void)
 	 *
@@ -4342,10 +4326,7 @@ RelationCacheInitializePhase3(void)

 	/*
@@ -6598,8 +6579,6 @@ write_relcache_init_file(bool shared)
 
 	/*
@@ -6659,9 +6638,7 @@ write_relcache_init_file(bool shared)
 	/*

 
 
 		/*

 

 

 
 
 	/*
 
index f8a6db5316a..92a98d00f5b 100644
@@ -35,7 +35,7 @@
 
@@ -603,14 +603,10 @@ PortalDrop(Portal portal, bool isTopCommit)
 void
 PortalHashTableDeleteAll(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
 	if (PortalHashTable == NULL)
 		return;

@@ -621,8 +617,7 @@ PortalHashTableDeleteAll(void)
 		PortalDrop(portal, false);

@@ -674,12 +669,8 @@ bool
 PreCommit_Portals(bool isPrepare)
 {
 	bool		result = false;
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
-
-	hash_seq_init(&status, PortalHashTable);

@@ -761,8 +752,7 @@ PreCommit_Portals(bool isPrepare)
 
 	return result;
@@ -777,12 +767,7 @@ PreCommit_Portals(bool isPrepare)
 void
 AtAbort_Portals(void)
 {
 
@@ -855,12 +840,7 @@ AtAbort_Portals(void)
 void
 AtCleanup_Portals(void)
 {
 
@@ -914,12 +894,7 @@ AtCleanup_Portals(void)
 void
 PortalErrorCleanup(void)
 {
 
@@ -943,12 +918,7 @@ AtSubCommit_Portals(SubTransactionId mySubid,
 					int parentLevel,
 
@@ -979,12 +949,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 
@@ -1089,12 +1054,7 @@ AtSubAbort_Portals(SubTransactionId mySubid,
 void
 
@@ -1129,8 +1089,6 @@ Datum
 pg_cursor(PG_FUNCTION_ARGS)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	HASH_SEQ_STATUS hash_seq;
-	PortalHashEnt *hentry;
 
 	/*
 	 * We put all the tuples into a tuplestore in one scan of the hashtable.
@@ -1138,8 +1096,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 	 */
 	InitMaterializedSRF(fcinfo, 0);
 
@@ -1168,12 +1125,7 @@ pg_cursor(PG_FUNCTION_ARGS)
 
@@ -1204,12 +1156,7 @@ ThereAreNoReadyPortals(void)
 void
 HoldPinnedPortals(void)
 {
 
@@ -1253,15 +1200,11 @@ HoldPinnedPortals(void)
 void
 ForgetPortalSnapshots(void)
 {
-	HASH_SEQ_STATUS status;
-	PortalHashEnt *hentry;
 	int			numPortalSnaps = 0;
 	int			numActiveSnaps = 0;

diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 15b8cacf57d..08c9832fb23 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -504,9 +504,6 @@ set_interp_require(bool trusted)
 static void
 plperl_fini(int code, Datum arg)
 {
-	HASH_SEQ_STATUS hash_seq;
-	plperl_interp_desc *interp_desc;
-
 	elog(DEBUG3, "plperl_fini");
 
 	/*
@@ -528,8 +525,7 @@ plperl_fini(int code, Datum arg)
 	plperl_destroy_interp(&plperl_held_interp);

From 376922f9831ab244f90a5b3b9cf61c668a552bfc Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 3 Dec 2025 23:33:18 +0100
Subject: [PATCH v12 5/5] Inline functions that have now become trivial

---

index 968cea8c926..24de0c37f74 100644
@@ -48,7 +48,6 @@
 
-static void InitQueryHashTable(void);
 static ParamListInfo EvaluateParams(ParseState *pstate,
 									PreparedStatement *pstmt, List *params,
 									EState *estate);
@@ -366,18 +365,6 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 	return paramLI;
 }
 
-
-/*
@@ -395,7 +382,9 @@ StorePreparedStatement(const char *stmt_name,

index af7a29df1f6..4524349d5e9 100644
 
-/*

 

-/*

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Thu, 02 Apr 2026 16:20:22 +0200

> test_cplusplusext test module.

Rebased on top of the latest master.

Sadly the new hash_make macros are failing to compile on C++ on MSVC 2019. I'll investigate later what we can do about that.
From 16c0bf5045984e358df88640940db603c058dd1a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 18 Mar 2026 09:37:32 +0100
Subject: [PATCH v13 0/5] Replace __builtin_types_compatible_p with _Generic

 
index fe22bc71d0c..cff9a6bb4f0 100755
 
   fi
 fi
index 6873b7546dd..667c566522a 100644
index 6bc74c2ba79..822efce9ba2 100644
--- a/meson.build
+++ b/meson.build
@@ -2070,21 +2070,6 @@ foreach builtin : builtins
 endforeach

index 88d13ec9993..6db88644491 100644
@@ -393,6 +393,23 @@ extern "C++"
 
+/*
@@ -1052,26 +1069,28 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,

index d8d61918aff..cf7abc56c1c 100644
@@ -541,9 +541,6 @@

 

 

base-commit: effaa464afd355e8927bf430cfe6a0ddd2ee5695
-- 
2.53.0

From ac0859238dbb85ed830f77695d4fc0bb9ffc7c25 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v13 1/5] Add hash_make macros

index 9ee1a2bfc6a..c7c05956820 100644
 
diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index f8317add68f..4071bfe404f 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -621,6 +621,121 @@ hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
 	return hashp;
 }
 
+/*
index 6db88644491..15451938e1e 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -83,6 +83,12 @@
 #ifdef ENABLE_NLS
 #include <libintl.h>
 #endif
+#ifdef __cplusplus
+extern "C++"
+{
+#include <type_traits>
+}
+#endif
 
 #ifdef __cplusplus
 extern "C++"
@@ -410,6 +416,18 @@ extern "C++"
 #define pg_expr_has_type_p(expr, type) _Generic((expr), type: 1, default: 0)
 #endif
 
+/*
index 2a9e9becd26..1672185cde1 100644
@@ -40,6 +40,38 @@ extern Size mul_size(Size s1, Size s2);

+/*
 
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 337b2e44625..3461cede65d 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -15,6 +15,9 @@
 #define HSEARCH_H

+/* Hash table control struct is an opaque type known only within dynahash.c */
+typedef struct HTAB HTAB;
+
 /*
  * Hash functions must have this signature.
  */
@@ -42,6 +45,156 @@ typedef void *(*HashCopyFunc) (void *dest, const void *src, Size keysize);
  */
 typedef void *(*HashAllocFunc) (Size request, void *alloc_arg);
 
+/*
@@ -56,11 +209,11 @@ typedef struct HASHELEMENT
 
@@ -129,6 +282,9 @@ typedef struct
 
 PG_MODULE_MAGIC;

index 5bc517602b1..e02318207e5 100644
@@ -1187,6 +1187,7 @@ HASHBUCKET
 HASHCTL
 HASHELEMENT
 HASHHDR
+HASHOPTS
 HASHSEGMENT
 HASH_SEQ_STATUS
 HE
-- 
2.53.0

From b57e4839cee514425de74b26141436a187a526dd Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:00 +0100
Subject: [PATCH v13 2/5] Add foreach_hash macro

diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 3461cede65d..a6208664101 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -277,6 +277,86 @@ typedef struct
 	uint32		hashvalue;		/* hashvalue to start seqscan over hash */
 } HASH_SEQ_STATUS;
 
@@ -296,7 +376,6 @@ extern void *hash_search_with_hash_value(HTAB *hashp, const void *keyPtr,

From 1de9607c1b05fa916f9cd5910b1dbc8f8cb6df70 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:36:19 +0100
Subject: [PATCH v13 3/5] Use hash_make macros throughout the codebase

 src/backend/storage/ipc/shmem.c               | 26 +++++++-
 72 files changed, 376 insertions(+), 847 deletions(-)

index 9798cb535bc..53a3a090d34 100644
@@ -2548,13 +2548,9 @@ getConnectionByName(const char *name)
 
 static remoteConn *
index 7975476b890..2af828a9991 100644

 

 
 
 		/*

 	/* Set up invalidation callback on pg_foreign_server. */
 	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 31f70b7bc10..092fce069e0 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -707,24 +707,17 @@ static HTAB *
 load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
 {
 	HTAB	   *crosstab_hash;
-	HASHCTL		ctl;
 	int			ret;
 	uint64		proc;
 	MemoryContext SPIcontext;

index f30346469ed..b72bdfde061 100644

 static void

 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8099b0d021f..fd66c070ca7 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -1360,7 +1360,6 @@ void
 _hash_finish_split(Relation rel, Buffer metabuf, Buffer obuf, Bucket obucket,
 				   uint32 maxbucket, uint32 highmask, uint32 lowmask)
 {
-	HASHCTL		hash_ctl;
 	HTAB	   *tidhtab;
 	Buffer		bucket_nbuf = InvalidBuffer;
 	Buffer		nbuf;
@@ -1371,16 +1370,8 @@ _hash_finish_split(Relation rel, Buffer metabuf, Buffer obuf, Bucket obucket,
 	Bucket		nbucket;
 	bool		found;

 
index f707b102c72..f58b4b2b205 100644
 
 	/*

 

 	/*

 /*

 

 
 /*
 
 /*
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 4b9802aafcc..e36419786d5 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -257,19 +257,12 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 {
 	/* hash table for O(1) rel_oid -> rel_numparents cell lookup */
 	HTAB	   *seen_rels;
-	HASHCTL		ctl;
 	List	   *rels_list,
 			   *rel_numparents;
 	ListCell   *l;

 	/*

 

 
 /*

 /*

 
 
 /*
 
 /*
index 0ce2e81f9c2..cb7e00ad03f 100644

index dfd7b33aa9b..aa93b4f3656 100644
@@ -5700,15 +5700,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)

 
 	if (*p_htable == NULL)
 	{
-		HASHCTL		ctl;
-
 
index 7c4be174869..80fc867da76 100644
 
 	/*

 
index 2f218c1ab8b..d1dd342d940 100644
 
@@ -1030,12 +1031,9 @@ find_oper_cache_entry(OprCacheKey *key)

 
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6694f485216..4cbb7ebf18c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -934,7 +934,6 @@ rebuild_database_list(Oid newdb)
 	MemoryContext newcxt;
 	MemoryContext oldcxt;
 	MemoryContext tmpcxt;
-	HASHCTL		hctl;
 	int			score;
 	int			nelems;
 	HTAB	   *dbhash;
@@ -964,12 +963,10 @@ rebuild_database_list(Oid newdb)
 	 * score, and finally put the array elements into the new doubly linked
 	 * list.
 	 */
-	hctl.keysize = sizeof(Oid);
-	hctl.entrysize = sizeof(avl_dbase);
-	hctl.hcxt = tmpcxt;
-	dbhash = hash_create("autovacuum db hash", 20, &hctl,	/* magic number here
-															 * FIXME */
-						 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	dbhash = hash_make_cxt(avl_dbase, adl_datid,
+						   "autovacuum db hash",
+						   20,	/* magic number here FIXME */
+						   tmpcxt);
 
 	/* start by inserting the new database */
 	score = 0;
@@ -1924,7 +1921,6 @@ do_autovacuum(void)
 	Form_pg_database dbForm;
 	List	   *tables_to_process = NIL;
 	List	   *orphan_oids = NIL;
-	HASHCTL		ctl;
 	HTAB	   *table_toast_map;
 	ListCell   *volatile cell;
 	BufferAccessStrategy bstrategy;
@@ -1997,13 +1993,9 @@ do_autovacuum(void)
 	pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel));

 	/*

 
 	/*

 

 

 

 

 
 /*
 
 	/*

 

 
 
 /*
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 396da84b25c..6f401e9656e 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -744,7 +744,6 @@ static void
 InitLocalBuffers(void)
 {
 	int			nbufs = num_temp_buffers;
-	HASHCTL		info;
 	int			i;
 
 	/*
@@ -795,13 +794,9 @@ InitLocalBuffers(void)
 	}

 	if (!LocalBufHash)
 
 		/*
 
 		/* Scan the directory. */
 		dbspace_dir = AllocateDir(dbspacedirname);
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index a5b7360e2dc..65128b0f508 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -179,8 +179,8 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 	 * Create (or attach to) the shared memory index of shmem areas.
 	 *
 	 * This is the same initialization as ShmemInitHash() does, but we cannot
-	 * use ShmemInitHash() here because it relies on ShmemIndex being already
-	 * initialized.
+	 * use ShmemInitHash() nor shmem_hash_make() here because it relies on
+	 * ShmemIndex being already initialized.
 	 */
 	info.keysize = SHMEM_INDEX_KEYSIZE;
 	info.entrysize = sizeof(ShmemIndexEnt);
@@ -388,6 +388,28 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 	return hash_create(name, init_size, infoP, hash_flags);
 }
 
+/*
index de9092fdf5b..65264999c8a 100644

 
 	/*
index 234643e4dd7..1d5ea7c051c 100644
@@ -444,7 +444,7 @@ static void GetSingleProcBlockerStatusData(PGPROC *blocked_proc,
@@ -460,15 +460,11 @@ LockManagerShmemInit(void)
 
@@ -478,16 +474,12 @@ LockManagerShmemInit(void)
 
 	/*
@@ -509,15 +501,9 @@ InitLockManagerAccess(void)

@@ -3406,20 +3392,13 @@ CheckForSessionAndXactLocks(void)
 		bool		xactLock;	/* is any lockmode held at xact level? */
 	} PerLockTagEntry;
 
-	HASHCTL		hash_ctl;
 	HTAB	   *lockhtab;
 	HASH_SEQ_STATUS status;
 	LOCALLOCK  *locallock;

 	/* Scan local lock table to find entries for each LOCKTAG */
 	hash_seq_init(&status, LockMethodLocalHash);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 5cb696490d6..8fea811ecbe 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -294,7 +294,6 @@ static lwlock_stats * get_lwlock_stats_entry(LWLock *lock);
 static void
 init_lwlock_stats(void)
 {
-	HASHCTL		ctl;
 	static MemoryContext lwlock_stats_cxt = NULL;
 	static bool exit_registered = false;
 
@@ -314,11 +313,8 @@ init_lwlock_stats(void)
 											 ALLOCSET_DEFAULT_SIZES);
 	MemoryContextAllowInCriticalSection(lwlock_stats_cxt, true);
 
index ae0e96aee5f..447b1e39804 100644
 	int64		max_predicate_lock_targets;
 	int64		max_predicate_locks;
 	int64		max_serializable_xacts;
@@ -1177,16 +1178,13 @@ PredicateLockShmemInit(void)
-											max_predicate_lock_targets,
-											max_predicate_lock_targets,
+												  max_predicate_lock_targets, max_predicate_lock_targets,
+												  &opts);
 
 	/*
@@ -1209,20 +1207,17 @@ PredicateLockShmemInit(void)
 	max_predicate_locks = max_predicate_lock_targets * 2;
 
-									  max_predicate_locks,
-									  max_predicate_locks,
+											max_predicate_locks, max_predicate_locks,
+											&opts);
 
 	/*
-										max_serializable_xacts,
-										max_serializable_xacts,
+											  max_serializable_xacts, max_serializable_xacts,
+											  &opts);
 
 	/*
@@ -1950,16 +1942,12 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
 
 /*

 /*

 

index e5a2289f0b0..3cf2d029d0c 100644
@@ -120,7 +120,6 @@ void
 
 	WaitEventCustomCounter = (WaitEventCustomCounterData *)
 		ShmemInitStruct("WaitEventCustomCounterData",
@@ -134,24 +133,18 @@ WaitEventCustomShmemInit(void)
 	}

 
 /*

 

index 0fee1b40d63..d6c018ca519 100644
@@ -901,19 +901,8 @@ json_unique_hash_match(const void *key1, const void *key2, Size keysize)
 
 static void
index 97cc3d60340..fc2e533a054 100644

 

 	return JSON_SUCCESS;
 }

 
index 2de08da6539..243fb2c4197 100644
@@ -3307,30 +3307,25 @@ ri_NullCheck(TupleDesc tupDesc,

index aec5556b008..f22d1bb7ce7 100644
@@ -4246,7 +4246,6 @@ static void
@@ -4262,13 +4261,9 @@ set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 
 	/* Preload the hash table with names appearing in parent_namespaces */
 	foreach(lc, parent_namespaces)
@@ -5339,7 +5334,6 @@ expand_colnames_array_to(deparse_columns *colinfo, int n)
 static void
 build_colinfo_names_hash(deparse_columns *colinfo)
 {
-	HASHCTL		hash_ctl;
 	int			i;
 	ListCell   *lc;
 
@@ -5355,13 +5349,10 @@ build_colinfo_names_hash(deparse_columns *colinfo)
 
 	/*

 
 
 	/*

 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e19f0d3e51c..79a291324c0 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1668,17 +1668,14 @@ LookupOpclassInfo(Oid operatorClassOid,

 
 	opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
@@ -3993,7 +3990,6 @@ RelationAssumeNewRelfilelocator(Relation relation)
 void
 RelationCacheInitialize(void)
 {
-	HASHCTL		ctl;
 	int			allocsize;
 
 	/*
@@ -4005,10 +4001,9 @@ RelationCacheInitialize(void)
 	/*
 
 	/*

 
index 9e29f1386b0..71e57076002 100644
@@ -118,12 +118,9 @@ lookup_ts_parser_cache(Oid prsId)
@@ -213,12 +210,9 @@ lookup_ts_dictionary_cache(Oid dictId)
@@ -364,12 +358,9 @@ lookup_ts_dictionary_cache(Oid dictId)

 

 
 
 /*
 
 	entry = (CFuncHashTabEntry *)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e1546d9c97a..cd99d72e8a8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -872,7 +872,6 @@ build_guc_variables(void)
 {
 	int			size_vars;
 	int			num_vars = 0;
-	HASHCTL		hash_ctl;
 	GUCHashEntry *hentry;
 	bool		found;
 
@@ -895,15 +894,10 @@ build_guc_variables(void)
 	 */
 	size_vars = num_vars + num_vars / 4;

 

 
 /*

 	/*
index 06ebffa111c..bc5bd5fa051 100644
 
 	if (inited)
 		return;
 
 	/*
 
 	/*
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 65b0fd0790f..579eece4c6f 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4015,8 +4015,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 EState *simple_eval_estate,
 					 ResourceOwner simple_eval_resowner)
 {
-	HASHCTL		ctl;
-
 	/* this link will be restored at exit from plpgsql_call_handler */
 	func->cur_estate = estate;
 
@@ -4071,12 +4069,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 
@@ -4084,13 +4080,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
@@ -4098,12 +4090,10 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,

 

 
 
 /*
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 85e83bbf1e3..8d5e8edbfe0 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -410,7 +410,6 @@ void
 _PG_init(void)
 {
 	Tcl_NotifierProcs notifier;
-	HASHCTL		hash_ctl;
 
@@ -448,22 +447,16 @@ _PG_init(void)

 
-- 
2.53.0

From 68cad956d293bb51c3591022fe7e349282e5c1f3 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 4 Dec 2025 15:39:09 +0100
Subject: [PATCH v13 4/5] Use foreach_hash macro throughout the codebase

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 53a3a090d34..d03669d163e 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -1279,14 +1279,11 @@ PG_FUNCTION_INFO_V1(dblink_get_connections);
 Datum
 dblink_get_connections(PG_FUNCTION_ARGS)
 {
-	HASH_SEQ_STATUS status;
-	remoteConnHashEnt *hentry;
 	ArrayBuildState *astate = NULL;
 
 	if (remoteConnHash)
 	{
index 2af828a9991..96621af5871 100644
 
@@ -1697,8 +1694,6 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 
 	/*
 	 * Superusers or roles with the privileges of pg_read_all_stats members
@@ -1828,8 +1823,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		}
 	}
 
-	hash_seq_init(&hash_seq, pgss_hash);
-	while ((entry = hash_seq_search(&hash_seq)) != NULL)
+	foreach_hash(pgssEntry, entry, pgss_hash)
 	{
 		Datum		values[PG_STAT_STATEMENTS_COLS];
 		bool		nulls[PG_STAT_STATEMENTS_COLS];
@@ -2174,9 +2168,7 @@ entry_cmp(const void *lhs, const void *rhs)
 static void
 entry_dealloc(void)
 {
-	HASH_SEQ_STATUS hash_seq;
 	pgssEntry **entries;
-	pgssEntry  *entry;
 	int			nvictims;
 	int			i;
 	Size		tottextlen;
@@ -2200,8 +2192,7 @@ entry_dealloc(void)
 	tottextlen = 0;
 	nvalidtexts = 0;
 
@@ -2513,8 +2504,6 @@ gc_qtexts(void)
 
@@ -2556,8 +2545,7 @@ gc_qtexts(void)
 	extent = 0;
 	nentries = 0;
 
@@ -2580,7 +2568,7 @@ gc_qtexts(void)
 
@@ -2648,8 +2636,7 @@ gc_fail:
@@ -2713,8 +2700,6 @@ if (e) { \
@@ -2734,6 +2719,8 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
@@ -2757,8 +2744,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
@@ -2771,8 +2757,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 
 	i = 0;

 
 	initStringInfo(&buf);

 

 

 

 
index f58b4b2b205..bc509082163 100644

 

 

 
 
 	if (!pendingSyncHash)

 
 
 	hash_destroy(tmphash);

 

 	/*

 	/* walk over cache */
index cb7e00ad03f..f4bc02bc4e8 100644

index d1dd342d940..5ff6a66152e 100644
@@ -1079,15 +1079,10 @@ static void

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 4cbb7ebf18c..0ddc21c09ad 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1048,8 +1048,6 @@ rebuild_database_list(Oid newdb)
 		TimestampTz current_time;
 		int			millis_increment;
 		avl_dbase  *dbary;
-		avl_dbase  *db;
-		HASH_SEQ_STATUS seq;
 		int			i;
 
 		/* put all the hash elements into an array */
@@ -1060,8 +1058,7 @@ rebuild_database_list(Oid newdb)
 #endif
 
 		i = 0;
 
 		/* sort the array */
@@ -1086,7 +1083,7 @@ rebuild_database_list(Oid newdb)
 		 */
 		for (i = 0; i < nelems; i++)
 		{
-			db = &(dbary[i]);
+			avl_dbase  *db = &(dbary[i]);

 

 

 
index 65128b0f508..50aa8d11a1f 100644
@@ -534,8 +534,6 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
@@ -544,11 +542,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)

@@ -591,8 +587,6 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
@@ -642,10 +636,8 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)

index 65264999c8a..1f16a8df5cc 100644
@@ -1101,13 +1101,9 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
 
@@ -1125,11 +1121,7 @@ StandbyReleaseAllLocks(void)
 
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 1d5ea7c051c..e5704cef90e 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -2304,11 +2304,9 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
 void
 LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 {
-	HASH_SEQ_STATUS status;
 	LockMethod	lockMethodTable;
 	int			i,
 				numLockModes;
-	LOCALLOCK  *locallock;
 	LOCK	   *lock;
 	int			partition;
 	bool		have_fast_path_lwlock = false;
@@ -2341,9 +2339,7 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 	 * pointers.  Fast-path locks are cleaned up during the locallock table
 	 * scan, though.
 	 */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		/*
 		 * If the LOCALLOCK entry is unused, something must've gone wrong
@@ -2578,15 +2574,10 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
 void
 LockReleaseSession(LOCKMETHODID lockmethodid)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
-
 	if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
 		elog(ERROR, "unrecognized lock method: %d", lockmethodid);
 
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		/* Ignore items that are not of the specified lock method */
 		if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid)
@@ -2610,12 +2601,7 @@ LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks)
 {
 	if (locallocks == NULL)
 	{
-		HASH_SEQ_STATUS status;
-		LOCALLOCK  *locallock;
-
-		hash_seq_init(&status, LockMethodLocalHash);
-
-		while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+		foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 			ReleaseLockIfHeld(locallock, false);
 	}
 	else
@@ -2709,12 +2695,7 @@ LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks)
 
 	if (locallocks == NULL)
 	{
@@ -3393,17 +3374,13 @@ CheckForSessionAndXactLocks(void)
 	} PerLockTagEntry;
 
 	HTAB	   *lockhtab;

@@ -3466,16 +3443,11 @@ CheckForSessionAndXactLocks(void)
 
 	/* Now do the per-locallock cleanup work */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		TwoPhaseLockRecord record;
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
@@ -3563,8 +3535,6 @@ void
 PostPrepare_Locks(FullTransactionId fxid)
 {
 	PGPROC	   *newproc = TwoPhaseGetDummyProc(fxid, false);
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	LOCK	   *lock;
 	PROCLOCK   *proclock;
 	PROCLOCKTAG proclocktag;
@@ -3586,9 +3556,7 @@ PostPrepare_Locks(FullTransactionId fxid)
 	 * pointing to the same proclock, and we daren't end up with any dangling
 	 * pointers.
 	 */
-	hash_seq_init(&status, LockMethodLocalHash);
-
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, LockMethodLocalHash)
 	{
 		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
 		bool		haveSessionLock;
@@ -3784,8 +3752,6 @@ LockData *
 GetLockStatusData(void)
 {
 	LockData   *data;
-	PROCLOCK   *proclock;
-	HASH_SEQ_STATUS seqstat;
 	int			els;
 	int			el;
 	int			i;
@@ -3921,9 +3887,7 @@ GetLockStatusData(void)
 	}
 
@@ -4161,8 +4125,6 @@ xl_standby_lock *
@@ -4184,10 +4146,9 @@ GetRunningTransactionLocks(int *nlocks)
 
@@ -4195,7 +4156,7 @@ GetRunningTransactionLocks(int *nlocks)
@@ -4290,18 +4251,14 @@ void
 
 	proc = MyProc;

 
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 8fea811ecbe..d3508e0b90e 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -325,15 +325,10 @@ init_lwlock_stats(void)
 static void
 print_lwlock_stats(int code, Datum arg)
 {
-	HASH_SEQ_STATUS scan;
-	lwlock_stats *lwstats;
-
-	hash_seq_init(&scan, lwlock_stats_htab);
-
 	/* Grab an LWLock to keep different backends from mixing reports */
 	LWLockAcquire(&MainLWLockArray[0].lock, LW_EXCLUSIVE);
 
index 447b1e39804..b385ca0d45e 100644
@@ -1453,8 +1453,6 @@ GetPredicateLockStatusData(void)

@@ -1474,11 +1472,9 @@ GetPredicateLockStatusData(void)

@@ -2935,8 +2931,6 @@ exit:
@@ -2992,9 +2986,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer)

@@ -4417,8 +4409,6 @@ CheckForSerializableConflictIn(Relation relation, const ItemPointerData *tid, Bl
 void
 CheckTableForSerializableConflictIn(Relation relation)
 {
-	HASH_SEQ_STATUS seqstat;
-	PREDICATELOCKTARGET *target;
 	Oid			dbId;
 	Oid			heapId;
 	int			i;
@@ -4452,9 +4442,7 @@ CheckTableForSerializableConflictIn(Relation relation)
 	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

 
 
-	HASH_SEQ_STATUS hstat;

 

 
 
index 3cf2d029d0c..c5db7582636 100644
@@ -300,8 +300,6 @@ char	  **
 
@@ -314,10 +312,8 @@ GetWaitEventCustomNames(uint32 classId, int *nwaitevents)
 	waiteventnames = palloc_array(char *, els);

 

 

 
index 79a291324c0..53c616fbe17 100644
@@ -177,7 +177,7 @@ static int	in_progress_list_maxlen;
@@ -2957,13 +2957,13 @@ RelationCacheInvalidateEntry(Oid relationId)
@@ -2980,8 +2980,6 @@ RelationCacheInvalidateEntry(Oid relationId)
@@ -2994,9 +2992,7 @@ RelationCacheInvalidate(bool debug_discard)
 
 	/* Phase 1 */
 
@@ -3141,12 +3137,9 @@ AssertPendingSyncConsistency(Relation relation)
 void
 AssertPendingSyncs_RelationCache(void)
 {
-	HASH_SEQ_STATUS status;
-	LOCALLOCK  *locallock;
 	Relation   *rels;
 	int			maxrels;
 	int			nrels;
-	RelIdCacheEnt *idhentry;
 	int			i;
 
 	/*
@@ -3160,8 +3153,7 @@ AssertPendingSyncs_RelationCache(void)
 	maxrels = 1;
 	rels = palloc(maxrels * sizeof(*rels));
 	nrels = 0;
-	hash_seq_init(&status, GetLockMethodLocalHash());
-	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+	foreach_hash(LOCALLOCK, locallock, GetLockMethodLocalHash())
 	{
 		Oid			relid;
 		Relation	r;
@@ -3183,8 +3175,7 @@ AssertPendingSyncs_RelationCache(void)
 		rels[nrels++] = r;
 	}

@@ -3212,8 +3203,6 @@ AssertPendingSyncs_RelationCache(void)
 
 	/*
@@ -3225,7 +3214,7 @@ AtEOXact_RelationCache(bool isCommit)
 
 	/*
@@ -3236,8 +3225,7 @@ AtEOXact_RelationCache(bool isCommit)
@@ -3246,10 +3234,11 @@ AtEOXact_RelationCache(bool isCommit)
@@ -3365,8 +3354,6 @@ void
 
 	/*
@@ -3379,13 +3366,12 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
 
 	/*
@@ -3395,6 +3381,8 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
@@ -4093,8 +4081,6 @@ RelationCacheInitializePhase2(void)
 
@@ -4225,15 +4211,13 @@ RelationCacheInitializePhase3(void)
 	 *
@@ -4343,10 +4327,7 @@ RelationCacheInitializePhase3(void)

 	/*
@@ -6599,8 +6580,6 @@ write_relcache_init_file(bool shared)
 
 	/*
@@ -6660,9 +6639,7 @@ write_relcache_init_file(bool shared)
 	/*
 
index 71e57076002..a8c808f87ed 100644
@@ -95,11 +95,8 @@ static void

 
 		/*
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cd99d72e8a8..d5c513d0bd0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -289,8 +289,6 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	const char *ConfFileWithError;
 	ConfigVariable *head,
 			   *tail;
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
 
@@ -365,8 +363,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 
@@ -450,8 +447,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 
@@ -840,8 +836,6 @@ struct config_generic **
 get_guc_variables(int *num_vars)
 {
 	struct config_generic **result;
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
 	int			i;
 
 	*num_vars = hash_get_num_entries(guc_hashtab);
@@ -849,8 +843,7 @@ get_guc_variables(int *num_vars)

@@ -1401,9 +1394,6 @@ check_GUC_init(const struct config_generic *gconf)
 void
 InitializeGUCOptions(void)
 {
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
-
 	/*
 	 * Before log_line_prefix could possibly receive a nonempty setting, make
 	 * sure that timezone processing is minimally alive (see elog.c).
@@ -1419,8 +1409,7 @@ InitializeGUCOptions(void)
 	 * Load all variables with their compiled-in defaults, and initialize
 	 * status fields as needed.
 	 */
-	hash_seq_init(&status, guc_hashtab);
-	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+	foreach_hash(GUCHashEntry, hentry, guc_hashtab)
 	{
 		/* Check mapping between initial and default value */
 		Assert(check_GUC_init(hentry->gucvar));
@@ -2446,9 +2435,6 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 void
 BeginReportingGUCOptions(void)
 {
-	HASH_SEQ_STATUS status;
-	GUCHashEntry *hentry;
-
 	/*
 	 * Don't do anything unless talking to an interactive frontend.
 	 */
@@ -2470,8 +2456,7 @@ BeginReportingGUCOptions(void)
 						PGC_INTERNAL, PGC_S_OVERRIDE);

@@ -5174,16 +5159,13 @@ void
 
 	/*

 

 

 
 	return result;

 

 
 
 	/*

 

index bc5bd5fa051..8ffee6368fc 100644
 
 	/*

From bef400aa62bf08d67af9d060b3e8a9264fcc90d2 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 3 Dec 2025 23:33:18 +0100
Subject: [PATCH v13 5/5] Inline functions that have now become trivial

---

 
-
-/*

 
-/*

 

-/*