From ade7d7f0ec65018c364429106808db16c68e2990 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Tue, 2 Dec 2025 08:07:20 +0000
Subject: [PATCH v1] Safer hash table initialization macro

Currently to create a hash table, we do things like:

ctl.keysize = sizeof(<Type>);
ctl.entrysize = sizeof(<Struct>);

but that lead to 2 possible issues:

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

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

To fix those, this commit introduces a new HASH_ELEM_INIT macro that:

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

so that:

- 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

Also note that this commit adds HASH_ELEM_INIT_FULL for the rare cases where
the whole struct is the key.
---
 contrib/dblink/dblink.c                       |  3 +-
 .../pg_stat_statements/pg_stat_statements.c   |  3 +-
 contrib/pg_trgm/trgm_regexp.c                 |  3 +-
 contrib/postgres_fdw/connection.c             |  3 +-
 contrib/postgres_fdw/shippable.c              |  3 +-
 contrib/tablefunc/tablefunc.c                 |  3 +-
 src/backend/access/common/heaptuple.c         |  3 +-
 src/backend/access/gist/gistbuild.c           |  3 +-
 src/backend/access/gist/gistbuildbuffers.c    |  3 +-
 src/backend/access/hash/hashpage.c            |  3 +-
 src/backend/access/heap/rewriteheap.c         |  8 ++---
 src/backend/access/transam/xlogprefetcher.c   |  3 +-
 src/backend/access/transam/xlogutils.c        |  3 +-
 src/backend/catalog/pg_enum.c                 |  6 ++--
 src/backend/catalog/pg_inherits.c             |  3 +-
 src/backend/catalog/storage.c                 |  6 ++--
 src/backend/commands/async.c                  |  3 +-
 src/backend/commands/prepare.c                |  3 +-
 src/backend/commands/sequence.c               |  3 +-
 src/backend/commands/tablecmds.c              |  3 +-
 src/backend/executor/nodeModifyTable.c        |  3 +-
 src/backend/nodes/extensible.c                |  3 +-
 src/backend/optimizer/util/plancat.c          |  3 +-
 src/backend/optimizer/util/predtest.c         |  3 +-
 src/backend/optimizer/util/relnode.c          |  3 +-
 src/backend/parser/parse_oper.c               |  3 +-
 src/backend/partitioning/partdesc.c           |  3 +-
 src/backend/postmaster/autovacuum.c           |  6 ++--
 src/backend/postmaster/checkpointer.c         |  3 +-
 .../replication/logical/applyparallelworker.c |  3 +-
 src/backend/replication/logical/relation.c    |  3 +-
 .../replication/logical/reorderbuffer.c       |  9 ++---
 src/backend/replication/logical/tablesync.c   |  3 +-
 src/backend/replication/pgoutput/pgoutput.c   |  3 +-
 src/backend/storage/buffer/buf_table.c        |  3 +-
 src/backend/storage/buffer/bufmgr.c           |  3 +-
 src/backend/storage/buffer/localbuf.c         |  3 +-
 src/backend/storage/file/reinit.c             |  3 +-
 src/backend/storage/ipc/shmem.c               |  3 +-
 src/backend/storage/ipc/standby.c             |  6 ++--
 src/backend/storage/lmgr/lock.c               | 12 +++----
 src/backend/storage/lmgr/lwlock.c             |  3 +-
 src/backend/storage/lmgr/predicate.c          | 12 +++----
 src/backend/storage/smgr/smgr.c               |  3 +-
 src/backend/storage/sync/sync.c               |  3 +-
 src/backend/tsearch/ts_typanalyze.c           |  3 +-
 src/backend/utils/activity/wait_event.c       |  6 ++--
 src/backend/utils/adt/array_typanalyze.c      |  6 ++--
 src/backend/utils/adt/json.c                  |  3 +-
 src/backend/utils/adt/jsonfuncs.c             |  6 ++--
 src/backend/utils/adt/mcxtfuncs.c             |  3 +-
 src/backend/utils/adt/ri_triggers.c           |  9 ++---
 src/backend/utils/adt/ruleutils.c             |  3 +-
 src/backend/utils/cache/attoptcache.c         |  3 +-
 src/backend/utils/cache/evtcache.c            |  3 +-
 src/backend/utils/cache/funccache.c           |  3 +-
 src/backend/utils/cache/relcache.c            |  6 ++--
 src/backend/utils/cache/relfilenumbermap.c    |  3 +-
 src/backend/utils/cache/spccache.c            |  3 +-
 src/backend/utils/cache/ts_cache.c            |  9 ++---
 src/backend/utils/cache/typcache.c            |  9 ++---
 src/backend/utils/fmgr/dfmgr.c                |  3 +-
 src/backend/utils/fmgr/fmgr.c                 |  3 +-
 src/backend/utils/misc/guc.c                  |  3 +-
 src/backend/utils/misc/injection_point.c      |  3 +-
 src/backend/utils/mmgr/portalmem.c            |  4 +--
 src/backend/utils/time/combocid.c             |  3 +-
 src/include/utils/hsearch.h                   | 35 +++++++++++++++++++
 src/pl/plperl/plperl.c                        |  9 ++---
 src/pl/plpgsql/src/pl_exec.c                  |  9 ++---
 src/pl/plpython/plpy_plpymodule.c             |  3 +-
 src/pl/plpython/plpy_procedure.c              |  3 +-
 src/pl/tcl/pltcl.c                            |  6 ++--
 src/timezone/pgtz.c                           |  3 +-
 74 files changed, 138 insertions(+), 203 deletions(-)
   5.0% contrib/
   8.2% src/backend/access/
   3.8% src/backend/catalog/
   3.6% src/backend/commands/
   6.2% src/backend/replication/logical/
   7.6% src/backend/storage/lmgr/
   8.3% src/backend/storage/
   8.5% src/backend/utils/adt/
  11.4% src/backend/utils/cache/
   7.7% src/backend/utils/
  10.6% src/backend/
   7.7% src/include/utils/
   3.0% src/pl/plpgsql/src/
   6.6% src/pl/

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 8bf8fc8ea2f..4e9c9d9e644 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2542,8 +2542,7 @@ createConnHash(void)
 {
 	HASHCTL		ctl;
 
-	ctl.keysize = NAMEDATALEN;
-	ctl.entrysize = sizeof(remoteConnHashEnt);
+	HASH_ELEM_INIT(ctl, remoteConnHashEnt, name);
 
 	return hash_create("Remote Con hash", NUMCONN, &ctl,
 					   HASH_ELEM | HASH_STRINGS);
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 39208f80b5b..0da50496827 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -557,8 +557,7 @@ pgss_shmem_startup(void)
 		pgss->stats.stats_reset = GetCurrentTimestamp();
 	}
 
-	info.keysize = sizeof(pgssHashKey);
-	info.entrysize = sizeof(pgssEntry);
+	HASH_ELEM_INIT(info, pgssEntry, key);
 	pgss_hash = ShmemInitHash("pg_stat_statements hash",
 							  pgss_max, pgss_max,
 							  &info,
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index 149f9eb259c..200ccfaeb9e 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -906,8 +906,7 @@ transformGraph(TrgmNFA *trgmNFA)
 	trgmNFA->overflowed = false;
 
 	/* Create hashtable for states */
-	hashCtl.keysize = sizeof(TrgmStateKey);
-	hashCtl.entrysize = sizeof(TrgmState);
+	HASH_ELEM_INIT(hashCtl, TrgmState, stateKey);
 	hashCtl.hcxt = CurrentMemoryContext;
 	trgmNFA->states = hash_create("Trigram NFA",
 								  1024,
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 953c2e0ab82..dee9c34d0fe 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -220,8 +220,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state)
 			pgfdw_we_get_result =
 				WaitEventExtensionNew("PostgresFdwGetResult");
 
-		ctl.keysize = sizeof(ConnCacheKey);
-		ctl.entrysize = sizeof(ConnCacheEntry);
+		HASH_ELEM_INIT(ctl, ConnCacheEntry, key);
 		ConnectionHash = hash_create("postgres_fdw connections", 8,
 									 &ctl,
 									 HASH_ELEM | HASH_BLOBS);
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
index da3b13b207d..578d20f41a3 100644
--- a/contrib/postgres_fdw/shippable.c
+++ b/contrib/postgres_fdw/shippable.c
@@ -93,8 +93,7 @@ InitializeShippableCache(void)
 	HASHCTL		ctl;
 
 	/* Create the hash table. */
-	ctl.keysize = sizeof(ShippableCacheKey);
-	ctl.entrysize = sizeof(ShippableCacheEntry);
+	HASH_ELEM_INIT(ctl, ShippableCacheEntry, key);
 	ShippableCacheHash =
 		hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
 
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 74afdc0977f..7bcff00af76 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -711,8 +711,7 @@ load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
 	MemoryContext SPIcontext;
 
 	/* initialize the category hash table */
-	ctl.keysize = MAX_CATNAME_LEN;
-	ctl.entrysize = sizeof(crosstab_HashEnt);
+	HASH_ELEM_INIT(ctl, crosstab_HashEnt, internal_catname);
 	ctl.hcxt = per_query_ctx;
 
 	/*
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 74a52d87067..3c7038d5805 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -127,8 +127,7 @@ init_missing_cache()
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = sizeof(missing_cache_key);
-	hash_ctl.entrysize = sizeof(missing_cache_key);
+	HASH_ELEM_INIT_FULL(hash_ctl, missing_cache_key);
 	hash_ctl.hcxt = TopMemoryContext;
 	hash_ctl.hash = missing_hash;
 	hash_ctl.match = missing_match;
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index be0fd5b753d..86ac61d869e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -1517,8 +1517,7 @@ gistInitParentMap(GISTBuildState *buildstate)
 {
 	HASHCTL		hashCtl;
 
-	hashCtl.keysize = sizeof(BlockNumber);
-	hashCtl.entrysize = sizeof(ParentMapEntry);
+	HASH_ELEM_INIT(hashCtl, ParentMapEntry, childblkno);
 	hashCtl.hcxt = CurrentMemoryContext;
 	buildstate->parentMap = hash_create("gistbuild parent map",
 										1024,
diff --git a/src/backend/access/gist/gistbuildbuffers.c b/src/backend/access/gist/gistbuildbuffers.c
index 0707254d18e..92bec12f03d 100644
--- a/src/backend/access/gist/gistbuildbuffers.c
+++ b/src/backend/access/gist/gistbuildbuffers.c
@@ -72,8 +72,7 @@ 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);
+	HASH_ELEM_INIT(hashCtl, GISTNodeBuffer, nodeBlocknum);
 	hashCtl.hcxt = CurrentMemoryContext;
 	gfbb->nodeBuffersTab = hash_create("gistbuildbuffers",
 									   1024,
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index b8e5bd005e5..1075af0c5d1 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -1368,8 +1368,7 @@ _hash_finish_split(Relation rel, Buffer metabuf, Buffer obuf, Bucket obucket,
 	bool		found;
 
 	/* Initialize hash tables used to track TIDs */
-	hash_ctl.keysize = sizeof(ItemPointerData);
-	hash_ctl.entrysize = sizeof(ItemPointerData);
+	HASH_ELEM_INIT_FULL(hash_ctl, ItemPointerData);
 	hash_ctl.hcxt = CurrentMemoryContext;
 
 	tidhtab =
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 66ab48f0fe0..8e70758f5dc 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -263,8 +263,7 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	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_ELEM_INIT(hash_ctl, UnresolvedTupData, key);
 	hash_ctl.hcxt = state->rs_cxt;
 
 	state->rs_unresolved_tups =
@@ -273,7 +272,7 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 					&hash_ctl,
 					HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
 
-	hash_ctl.entrysize = sizeof(OldToNewMappingData);
+	HASH_ELEM_INIT(hash_ctl, OldToNewMappingData, key);
 
 	state->rs_old_new_tid_map =
 		hash_create("Rewrite / Old to new tid map",
@@ -788,8 +787,7 @@ 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_ELEM_INIT(hash_ctl, RewriteMappingFile, xid);
 	hash_ctl.hcxt = state->rs_cxt;
 
 	state->rs_logical_mappings =
diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index ed3aacabc98..4a22432cbf3 100644
--- a/src/backend/access/transam/xlogprefetcher.c
+++ b/src/backend/access/transam/xlogprefetcher.c
@@ -367,8 +367,7 @@ XLogPrefetcherAllocate(XLogReaderState *reader)
 	prefetcher = palloc0(sizeof(XLogPrefetcher));
 	prefetcher->reader = reader;
 
-	ctl.keysize = sizeof(RelFileLocator);
-	ctl.entrysize = sizeof(XLogPrefetcherFilter);
+	HASH_ELEM_INIT(ctl, XLogPrefetcherFilter, rlocator);
 	prefetcher->filter_table = hash_create("XLogPrefetcherFilterTable", 1024,
 										   &ctl, HASH_ELEM | HASH_BLOBS);
 	dlist_init(&prefetcher->filter_queue);
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index ce2a3e42146..15b5002c105 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -133,8 +133,7 @@ log_invalid_page(RelFileLocator locator, ForkNumber forkno, BlockNumber blkno,
 		/* create hash table when first needed */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(xl_invalid_page_key);
-		ctl.entrysize = sizeof(xl_invalid_page);
+		HASH_ELEM_INIT(ctl, xl_invalid_page, key);
 
 		invalid_page_tab = hash_create("XLOG invalid-page table",
 									   100,
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index da9c2a46cfa..4b112b25673 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -269,8 +269,7 @@ init_uncommitted_enum_types(void)
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(Oid);
+	HASH_ELEM_INIT_FULL(hash_ctl, Oid);
 	hash_ctl.hcxt = TopTransactionContext;
 	uncommitted_enum_types = hash_create("Uncommitted enum types",
 										 32,
@@ -286,8 +285,7 @@ init_uncommitted_enum_values(void)
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(Oid);
+	HASH_ELEM_INIT_FULL(hash_ctl, Oid);
 	hash_ctl.hcxt = TopTransactionContext;
 	uncommitted_enum_values = hash_create("Uncommitted enum values",
 										  32,
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 929bb53b620..2fd39535156 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -261,8 +261,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 			   *rel_numparents;
 	ListCell   *l;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SeenRelsEntry);
+	HASH_ELEM_INIT(ctl, SeenRelsEntry, rel_id);
 	ctl.hcxt = CurrentMemoryContext;
 
 	seen_rels = hash_create("find_all_inheritors temporary table",
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c58e9418ac3..b3f74dc1dbc 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -93,8 +93,7 @@ AddPendingSync(const RelFileLocator *rlocator)
 	{
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(RelFileLocator);
-		ctl.entrysize = sizeof(PendingRelSync);
+		HASH_ELEM_INIT(ctl, PendingRelSync, rlocator);
 		ctl.hcxt = TopTransactionContext;
 		pendingSyncHash = hash_create("pending sync hash", 16, &ctl,
 									  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
@@ -611,8 +610,7 @@ SerializePendingSyncs(Size maxSize, char *startAddress)
 		goto terminate;
 
 	/* Create temporary hash to collect active relfilelocators */
-	ctl.keysize = sizeof(RelFileLocator);
-	ctl.entrysize = sizeof(RelFileLocator);
+	HASH_ELEM_INIT_FULL(ctl, RelFileLocator);
 	ctl.hcxt = CurrentMemoryContext;
 	tmphash = hash_create("tmp relfilelocators",
 						  hash_get_num_entries(pendingSyncHash), &ctl,
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index eb86402cae4..b9d7bc9b0b8 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -2418,8 +2418,7 @@ AddEventToPendingNotifies(Notification *n)
 		ListCell   *l;
 
 		/* Create the hash table */
-		hash_ctl.keysize = sizeof(Notification *);
-		hash_ctl.entrysize = sizeof(struct NotificationHash);
+		HASH_ELEM_INIT(hash_ctl, struct NotificationHash, event);
 		hash_ctl.hash = notification_hash;
 		hash_ctl.match = notification_match;
 		hash_ctl.hcxt = CurTransactionContext;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 34b6410d6a2..64430de2b56 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -373,8 +373,7 @@ InitQueryHashTable(void)
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = NAMEDATALEN;
-	hash_ctl.entrysize = sizeof(PreparedStatement);
+	HASH_ELEM_INIT(hash_ctl, PreparedStatement, stmt_name);
 
 	prepared_queries = hash_create("Prepared Queries",
 								   32,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 51567994126..cc03a3eebb1 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1115,8 +1115,7 @@ create_seq_hashtable(void)
 {
 	HASHCTL		ctl;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SeqTableData);
+	HASH_ELEM_INIT(ctl, SeqTableData, relid);
 
 	seqhashtab = hash_create("Sequence values", 16, &ctl,
 							 HASH_ELEM | HASH_BLOBS);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 07e5b95782e..abb67da2dd9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2158,8 +2158,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 				HASHCTL		hctl;
 
 				memset(&hctl, 0, sizeof(HASHCTL));
-				hctl.keysize = sizeof(Oid);
-				hctl.entrysize = sizeof(ForeignTruncateInfo);
+				HASH_ELEM_INIT(hctl, ForeignTruncateInfo, serverid);
 				hctl.hcxt = CurrentMemoryContext;
 
 				ft_htab = hash_create("TRUNCATE for Foreign Tables",
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e44f1223886..8d97d0cd66e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -5136,8 +5136,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		HASHCTL		hash_ctl;
 
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(MTTargetRelLookup);
+		HASH_ELEM_INIT(hash_ctl, MTTargetRelLookup, relationOid);
 		hash_ctl.hcxt = CurrentMemoryContext;
 		mtstate->mt_resultOidHash =
 			hash_create("ModifyTable target hash",
diff --git a/src/backend/nodes/extensible.c b/src/backend/nodes/extensible.c
index 3ede1ee0f5d..f4f6b659f9f 100644
--- a/src/backend/nodes/extensible.c
+++ b/src/backend/nodes/extensible.c
@@ -47,8 +47,7 @@ RegisterExtensibleNodeEntry(HTAB **p_htable, const char *htable_label,
 	{
 		HASHCTL		ctl;
 
-		ctl.keysize = EXTNODENAME_MAX_LEN;
-		ctl.entrysize = sizeof(ExtensibleNodeEntry);
+		HASH_ELEM_INIT(ctl, ExtensibleNodeEntry, extnodename);
 
 		*p_htable = hash_create(htable_label, 100, &ctl,
 								HASH_ELEM | HASH_STRINGS);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 07f92fac239..36e9814cac5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -697,8 +697,7 @@ get_relation_notnullatts(PlannerInfo *root, Relation relation)
 		HTAB	   *hashtab;
 		HASHCTL		hash_ctl;
 
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(NotnullHashEntry);
+		HASH_ELEM_INIT(hash_ctl, NotnullHashEntry, relid);
 		hash_ctl.hcxt = CurrentMemoryContext;
 
 		hashtab = hash_create("Relation NOT NULL attnums",
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index ac28573cd0a..0661d68a6c2 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -2119,8 +2119,7 @@ lookup_proof_cache(Oid pred_op, Oid clause_op, bool refute_it)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(OprProofCacheKey);
-		ctl.entrysize = sizeof(OprProofCacheEntry);
+		HASH_ELEM_INIT(ctl, OprProofCacheEntry, key);
 		OprProofCacheHash = hash_create("Btree proof lookup cache", 256,
 										&ctl, HASH_ELEM | HASH_BLOBS);
 
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 1158bc194c3..52bfab04567 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -605,8 +605,7 @@ build_join_rel_hash(PlannerInfo *root)
 	ListCell   *l;
 
 	/* Create the hash table */
-	hash_ctl.keysize = sizeof(Relids);
-	hash_ctl.entrysize = sizeof(JoinHashEntry);
+	HASH_ELEM_INIT(hash_ctl, JoinHashEntry, join_relids);
 	hash_ctl.hash = bitmap_hash;
 	hash_ctl.match = bitmap_match;
 	hash_ctl.hcxt = CurrentMemoryContext;
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 7bd7a336fd6..9c1bfa05ebf 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -1030,8 +1030,7 @@ find_oper_cache_entry(OprCacheKey *key)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(OprCacheKey);
-		ctl.entrysize = sizeof(OprCacheEntry);
+		HASH_ELEM_INIT(ctl, OprCacheEntry, key);
 		OprCacheHash = hash_create("Operator lookup cache", 256,
 								   &ctl, HASH_ELEM | HASH_BLOBS);
 
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index 328b4d450e4..e6f7b26ee98 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -429,8 +429,7 @@ CreatePartitionDirectory(MemoryContext mcxt, bool omit_detached)
 	pdir = palloc(sizeof(PartitionDirectoryData));
 	pdir->pdir_mcxt = mcxt;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(PartitionDirectoryEntry);
+	HASH_ELEM_INIT(ctl, PartitionDirectoryEntry, reloid);
 	ctl.hcxt = mcxt;
 
 	pdir->pdir_hash = hash_create("partition directory", 256, &ctl,
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1c38488f2cb..319fb82ebcc 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -937,8 +937,7 @@ 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);
+	HASH_ELEM_INIT(hctl, avl_dbase, adl_datid);
 	hctl.hcxt = tmpcxt;
 	dbhash = hash_create("autovacuum db hash", 20, &hctl,	/* magic number here
 															 * FIXME */
@@ -1977,8 +1976,7 @@ 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);
+	HASH_ELEM_INIT(ctl, av_relation, ar_toastrelid);
 
 	table_toast_map = hash_create("TOAST to main relid map",
 								  100,
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e84e8663e96..972b61ec67e 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -1321,8 +1321,7 @@ CompactCheckpointerRequestQueue(void)
 	head = CheckpointerShmem->head;
 
 	/* Initialize temporary hash table */
-	ctl.keysize = sizeof(CheckpointerRequest);
-	ctl.entrysize = sizeof(struct CheckpointerSlotMapping);
+	HASH_ELEM_INIT(ctl, struct CheckpointerSlotMapping, request);
 	ctl.hcxt = CurrentMemoryContext;
 
 	htab = hash_create("CompactCheckpointerRequestQueue",
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index baa68c1ab6c..43c91ad5179 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -487,8 +487,7 @@ pa_allocate_worker(TransactionId xid)
 		HASHCTL		ctl;
 
 		MemSet(&ctl, 0, sizeof(ctl));
-		ctl.keysize = sizeof(TransactionId);
-		ctl.entrysize = sizeof(ParallelApplyWorkerEntry);
+		HASH_ELEM_INIT(ctl, ParallelApplyWorkerEntry, xid);
 		ctl.hcxt = ApplyContext;
 
 		ParallelApplyTxnHash = hash_create("logical replication parallel apply workers hash",
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 10b3d0d9b82..86061323bc1 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -619,8 +619,7 @@ logicalrep_partmap_init(void)
 								  ALLOCSET_DEFAULT_SIZES);
 
 	/* Initialize the relation hash table. */
-	ctl.keysize = sizeof(Oid);	/* partition OID */
-	ctl.entrysize = sizeof(LogicalRepPartMapEntry);
+	HASH_ELEM_INIT(ctl, LogicalRepPartMapEntry, partoid);
 	ctl.hcxt = LogicalRepPartMapContext;
 
 	LogicalRepPartMap = hash_create("logicalrep partition map cache", 64, &ctl,
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index eb6a84554b7..d2c2ee823c9 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -367,8 +367,7 @@ ReorderBufferAllocate(void)
 												  SLAB_DEFAULT_BLOCK_SIZE,
 												  SLAB_DEFAULT_BLOCK_SIZE);
 
-	hash_ctl.keysize = sizeof(TransactionId);
-	hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
+	HASH_ELEM_INIT(hash_ctl, ReorderBufferTXNByIdEnt, xid);
 	hash_ctl.hcxt = buffer->context;
 
 	buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
@@ -1841,8 +1840,7 @@ ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 	if (!rbtxn_has_catalog_changes(txn) || dlist_is_empty(&txn->tuplecids))
 		return;
 
-	hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
-	hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
+	HASH_ELEM_INIT(hash_ctl, ReorderBufferTupleCidEnt, key);
 	hash_ctl.hcxt = rb->context;
 
 	/*
@@ -4981,8 +4979,7 @@ ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
 
 	Assert(txn->toast_hash == NULL);
 
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
+	HASH_ELEM_INIT(hash_ctl, ReorderBufferToastEnt, chunk_id);
 	hash_ctl.hcxt = rb->context;
 	txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
 								  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index fa8e3bf969a..e9adaef36a9 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -392,8 +392,7 @@ ProcessSyncingTablesForApply(XLogRecPtr current_lsn)
 	{
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(struct tablesync_start_time_mapping);
+		HASH_ELEM_INIT(ctl, struct tablesync_start_time_mapping, relid);
 		last_start_times = hash_create("Logical replication table sync worker start times",
 									   256, &ctl, HASH_ELEM | HASH_BLOBS);
 	}
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 942e1abdb58..755c2def1aa 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1977,8 +1977,7 @@ init_rel_sync_cache(MemoryContext cachectx)
 		return;
 
 	/* Make a new hash table for the cache */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RelationSyncEntry);
+	HASH_ELEM_INIT(ctl, RelationSyncEntry, relid);
 	ctl.hcxt = cachectx;
 
 	RelationSyncCache = hash_create("logical replication output relation cache",
diff --git a/src/backend/storage/buffer/buf_table.c b/src/backend/storage/buffer/buf_table.c
index 9d256559bab..350153ead43 100644
--- a/src/backend/storage/buffer/buf_table.c
+++ b/src/backend/storage/buffer/buf_table.c
@@ -55,8 +55,7 @@ InitBufTable(int size)
 	/* assume no locking is needed yet */
 
 	/* BufferTag maps to Buffer */
-	info.keysize = sizeof(BufferTag);
-	info.entrysize = sizeof(BufferLookupEnt);
+	HASH_ELEM_INIT(info, BufferLookupEnt, key);
 	info.num_partitions = NUM_BUFFER_PARTITIONS;
 
 	SharedBufHash = ShmemInitHash("Shared Buffer Lookup Table",
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f373cead95f..83a2232a6f6 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -4019,8 +4019,7 @@ InitBufferManagerAccess(void)
 
 	memset(&PrivateRefCountArray, 0, sizeof(PrivateRefCountArray));
 
-	hash_ctl.keysize = sizeof(int32);
-	hash_ctl.entrysize = sizeof(PrivateRefCountEntry);
+	HASH_ELEM_INIT(hash_ctl, PrivateRefCountEntry, buffer);
 
 	PrivateRefCountHash = hash_create("PrivateRefCount", 100, &hash_ctl,
 									  HASH_ELEM | HASH_BLOBS);
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 15aac7d1c9f..8b9e09ed4b0 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -779,8 +779,7 @@ InitLocalBuffers(void)
 	}
 
 	/* Create the lookup hash table */
-	info.keysize = sizeof(BufferTag);
-	info.entrysize = sizeof(LocalBufferLookupEnt);
+	HASH_ELEM_INIT(info, LocalBufferLookupEnt, key);
 
 	LocalBufHash = hash_create("Local Buffer Lookup Table",
 							   nbufs,
diff --git a/src/backend/storage/file/reinit.c b/src/backend/storage/file/reinit.c
index 5c8275cf536..6a1438eadb4 100644
--- a/src/backend/storage/file/reinit.c
+++ b/src/backend/storage/file/reinit.c
@@ -184,8 +184,7 @@ 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);
+		HASH_ELEM_INIT(ctl, unlogged_relation_entry, relnumber);
 		ctl.hcxt = CurrentMemoryContext;
 		hash = hash_create("unlogged relation OIDs", 32, &ctl,
 						   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index ee3408df301..15d7149c954 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -294,8 +294,7 @@ 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);
+	HASH_ELEM_INIT(info, ShmemIndexEnt, key);
 
 	ShmemIndex = ShmemInitHash("ShmemIndex",
 							   SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE,
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 4222bdab078..eca82020587 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -103,14 +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);
+	HASH_ELEM_INIT(hash_ctl, RecoveryLockEntry, key);
 	RecoveryLockHash = hash_create("RecoveryLockHash",
 								   64,
 								   &hash_ctl,
 								   HASH_ELEM | HASH_BLOBS);
-	hash_ctl.keysize = sizeof(TransactionId);
-	hash_ctl.entrysize = sizeof(RecoveryLockXidEntry);
+	HASH_ELEM_INIT(hash_ctl, RecoveryLockXidEntry, xid);
 	RecoveryLockXidHash = hash_create("RecoveryLockXidHash",
 									  64,
 									  &hash_ctl,
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 9cb78ead105..e2f39d41406 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -459,8 +459,7 @@ LockManagerShmemInit(void)
 	 * Allocate hash table for LOCK structs.  This stores per-locked-object
 	 * information.
 	 */
-	info.keysize = sizeof(LOCKTAG);
-	info.entrysize = sizeof(LOCK);
+	HASH_ELEM_INIT(info, LOCK, tag);
 	info.num_partitions = NUM_LOCK_PARTITIONS;
 
 	LockMethodLockHash = ShmemInitHash("LOCK hash",
@@ -477,8 +476,7 @@ LockManagerShmemInit(void)
 	 * Allocate hash table for PROCLOCK structs.  This stores
 	 * per-lock-per-holder information.
 	 */
-	info.keysize = sizeof(PROCLOCKTAG);
-	info.entrysize = sizeof(PROCLOCK);
+	HASH_ELEM_INIT(info, PROCLOCK, tag);
 	info.hash = proclock_hash;
 	info.num_partitions = NUM_LOCK_PARTITIONS;
 
@@ -510,8 +508,7 @@ InitLockManagerAccess(void)
 	 */
 	HASHCTL		info;
 
-	info.keysize = sizeof(LOCALLOCKTAG);
-	info.entrysize = sizeof(LOCALLOCK);
+	HASH_ELEM_INIT(info, LOCALLOCK, tag);
 
 	LockMethodLocalHash = hash_create("LOCALLOCK hash",
 									  16,
@@ -3402,8 +3399,7 @@ CheckForSessionAndXactLocks(void)
 	LOCALLOCK  *locallock;
 
 	/* Create a local hash table keyed by LOCKTAG only */
-	hash_ctl.keysize = sizeof(LOCKTAG);
-	hash_ctl.entrysize = sizeof(PerLockTagEntry);
+	HASH_ELEM_INIT(hash_ctl, PerLockTagEntry, lock);
 	hash_ctl.hcxt = CurrentMemoryContext;
 
 	lockhtab = hash_create("CheckForSessionAndXactLocks table",
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 255cfa8fa95..47aed6c13c5 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -305,8 +305,7 @@ init_lwlock_stats(void)
 											 ALLOCSET_DEFAULT_SIZES);
 	MemoryContextAllowInCriticalSection(lwlock_stats_cxt, true);
 
-	ctl.keysize = sizeof(lwlock_stats_key);
-	ctl.entrysize = sizeof(lwlock_stats);
+	HASH_ELEM_INIT(ctl, lwlock_stats, key);
 	ctl.hcxt = lwlock_stats_cxt;
 	lwlock_stats_htab = hash_create("lwlock stats", 16384, &ctl,
 									HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index f12f8f77aad..571fa39b704 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -1163,8 +1163,7 @@ PredicateLockShmemInit(void)
 	 * Allocate hash table for PREDICATELOCKTARGET structs.  This stores
 	 * per-predicate-lock-target information.
 	 */
-	info.keysize = sizeof(PREDICATELOCKTARGETTAG);
-	info.entrysize = sizeof(PREDICATELOCKTARGET);
+	HASH_ELEM_INIT(info, PREDICATELOCKTARGET, tag);
 	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
 
 	PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash",
@@ -1195,8 +1194,7 @@ 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);
+	HASH_ELEM_INIT(info, PREDICATELOCK, tag);
 	info.hash = predicatelock_hash;
 	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
 
@@ -1282,8 +1280,7 @@ 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);
+	HASH_ELEM_INIT(info, SERIALIZABLEXID, tag);
 
 	SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash",
 										max_table_size,
@@ -1943,8 +1940,7 @@ CreateLocalPredicateLockHash(void)
 
 	/* Initialize the backend-local hash table of parent locks */
 	Assert(LocalPredicateLockHash == NULL);
-	hash_ctl.keysize = sizeof(PREDICATELOCKTARGETTAG);
-	hash_ctl.entrysize = sizeof(LOCALPREDICATELOCK);
+	HASH_ELEM_INIT(hash_ctl, LOCALPREDICATELOCK, tag);
 	LocalPredicateLockHash = hash_create("Local predicate lock",
 										 max_predicate_locks_per_xact,
 										 &hash_ctl,
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index bce37a36d51..22da64f0bad 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -252,8 +252,7 @@ smgropen(RelFileLocator rlocator, ProcNumber backend)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(RelFileLocatorBackend);
-		ctl.entrysize = sizeof(SMgrRelationData);
+		HASH_ELEM_INIT(ctl, SMgrRelationData, smgr_rlocator);
 		SMgrRelationHash = hash_create("smgr relation table", 400,
 									   &ctl, HASH_ELEM | HASH_BLOBS);
 		dlist_init(&unpinned_relns);
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index fc16db90133..654d4aeb51e 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -146,8 +146,7 @@ InitSync(void)
 											  ALLOCSET_DEFAULT_SIZES);
 		MemoryContextAllowInCriticalSection(pendingOpsCxt, true);
 
-		hash_ctl.keysize = sizeof(FileTag);
-		hash_ctl.entrysize = sizeof(PendingFsyncEntry);
+		HASH_ELEM_INIT(hash_ctl, PendingFsyncEntry, tag);
 		hash_ctl.hcxt = pendingOpsCxt;
 		pendingOps = hash_create("Pending Ops Table",
 								 100L,
diff --git a/src/backend/tsearch/ts_typanalyze.c b/src/backend/tsearch/ts_typanalyze.c
index 93aab00a3ca..3415b930fb5 100644
--- a/src/backend/tsearch/ts_typanalyze.c
+++ b/src/backend/tsearch/ts_typanalyze.c
@@ -180,8 +180,7 @@ 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_ELEM_INIT(hash_ctl, TrackItem, key);
 	hash_ctl.hash = lexeme_hash;
 	hash_ctl.match = lexeme_match;
 	hash_ctl.hcxt = CurrentMemoryContext;
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index d9b8f34a355..b47de48f5d7 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -133,8 +133,7 @@ WaitEventCustomShmemInit(void)
 	}
 
 	/* initialize or attach the hash tables to store custom wait events */
-	info.keysize = sizeof(uint32);
-	info.entrysize = sizeof(WaitEventCustomEntryByInfo);
+	HASH_ELEM_INIT(info, WaitEventCustomEntryByInfo, wait_event_info);
 	WaitEventCustomHashByInfo =
 		ShmemInitHash("WaitEventCustom hash by wait event information",
 					  WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
@@ -143,8 +142,7 @@ WaitEventCustomShmemInit(void)
 					  HASH_ELEM | HASH_BLOBS);
 
 	/* key is a NULL-terminated string */
-	info.keysize = sizeof(char[NAMEDATALEN]);
-	info.entrysize = sizeof(WaitEventCustomEntryByName);
+	HASH_ELEM_INIT(info, WaitEventCustomEntryByName, wait_event_name);
 	WaitEventCustomHashByName =
 		ShmemInitHash("WaitEventCustom hash by name",
 					  WAIT_EVENT_CUSTOM_HASH_INIT_SIZE,
diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c
index 560b27f3ca7..db68c7a4127 100644
--- a/src/backend/utils/adt/array_typanalyze.c
+++ b/src/backend/utils/adt/array_typanalyze.c
@@ -276,8 +276,7 @@ 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);
+	HASH_ELEM_INIT(elem_hash_ctl, TrackItem, key);
 	elem_hash_ctl.hash = element_hash;
 	elem_hash_ctl.match = element_match;
 	elem_hash_ctl.hcxt = CurrentMemoryContext;
@@ -287,8 +286,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 							   HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
 
 	/* hashtable for array distinct elements counts */
-	count_hash_ctl.keysize = sizeof(int);
-	count_hash_ctl.entrysize = sizeof(DECountItem);
+	HASH_ELEM_INIT(count_hash_ctl, DECountItem, count);
 	count_hash_ctl.hcxt = CurrentMemoryContext;
 	count_tab = hash_create("Array distinct element count table",
 							64,
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 06dd62f0008..39e1adee19f 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -936,8 +936,7 @@ json_unique_check_init(JsonUniqueCheckState *cxt)
 	HASHCTL		ctl;
 
 	memset(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(JsonUniqueHashEntry);
-	ctl.entrysize = sizeof(JsonUniqueHashEntry);
+	HASH_ELEM_INIT_FULL(ctl, JsonUniqueHashEntry);
 	ctl.hcxt = CurrentMemoryContext;
 	ctl.hash = json_unique_hash;
 	ctl.match = json_unique_hash_match;
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index de32e329975..e7d06bdcde0 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3816,8 +3816,7 @@ get_json_object_as_hash(const char *json, int len, const char *funcname,
 	JHashState *state;
 	JsonSemAction *sem;
 
-	ctl.keysize = NAMEDATALEN;
-	ctl.entrysize = sizeof(JsonHashEntry);
+	HASH_ELEM_INIT(ctl, JsonHashEntry, fname);
 	ctl.hcxt = CurrentMemoryContext;
 	tab = hash_create("json object hashtable",
 					  100,
@@ -4230,8 +4229,7 @@ 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);
+	HASH_ELEM_INIT(ctl, JsonHashEntry, fname);
 	ctl.hcxt = CurrentMemoryContext;
 	_state->json_hash = hash_create("json object hashtable",
 									100,
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index fe6dce9cba3..700403b7d3f 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -188,8 +188,7 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 	HASHCTL		ctl;
 	HTAB	   *context_id_lookup;
 
-	ctl.keysize = sizeof(MemoryContext);
-	ctl.entrysize = sizeof(MemoryContextId);
+	HASH_ELEM_INIT(ctl, MemoryContextId, context);
 	ctl.hcxt = CurrentMemoryContext;
 
 	context_id_lookup = hash_create("pg_get_backend_memory_contexts",
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..c48b8d3e1b2 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2859,8 +2859,7 @@ ri_InitHashTables(void)
 {
 	HASHCTL		ctl;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RI_ConstraintInfo);
+	HASH_ELEM_INIT(ctl, RI_ConstraintInfo, constraint_id);
 	ri_constraint_cache = hash_create("RI constraint cache",
 									  RI_INIT_CONSTRAINTHASHSIZE,
 									  &ctl, HASH_ELEM | HASH_BLOBS);
@@ -2870,14 +2869,12 @@ ri_InitHashTables(void)
 								  InvalidateConstraintCacheCallBack,
 								  (Datum) 0);
 
-	ctl.keysize = sizeof(RI_QueryKey);
-	ctl.entrysize = sizeof(RI_QueryHashEntry);
+	HASH_ELEM_INIT(ctl, RI_QueryHashEntry, key);
 	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);
+	HASH_ELEM_INIT(ctl, RI_CompareHashEntry, key);
 	ri_compare_cache = hash_create("RI compare cache",
 								   RI_INIT_QUERYHASHSIZE,
 								   &ctl, HASH_ELEM | HASH_BLOBS);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6cf90be40bb..e2af22c6fb5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3903,8 +3903,7 @@ 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_ELEM_INIT(hash_ctl, NameHashEntry, name);
 	hash_ctl.hcxt = CurrentMemoryContext;
 	names_hash = hash_create("set_rtable_names names",
 							 list_length(dpns->rtable),
diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c
index 45d1e2be007..74f6221e737 100644
--- a/src/backend/utils/cache/attoptcache.c
+++ b/src/backend/utils/cache/attoptcache.c
@@ -99,8 +99,7 @@ InitializeAttoptCache(void)
 	HASHCTL		ctl;
 
 	/* Initialize the hash table. */
-	ctl.keysize = sizeof(AttoptCacheKey);
-	ctl.entrysize = sizeof(AttoptCacheEntry);
+	HASH_ELEM_INIT(ctl, AttoptCacheEntry, key);
 
 	/*
 	 * AttoptCacheEntry takes hash value from the system cache. For
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index b9d5a5998be..1eaecebbf58 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -113,8 +113,7 @@ BuildEventTriggerCache(void)
 	EventTriggerCacheState = ETCS_REBUILD_STARTED;
 
 	/* Create new hash table. */
-	ctl.keysize = sizeof(EventTriggerEvent);
-	ctl.entrysize = sizeof(EventTriggerCacheEntry);
+	HASH_ELEM_INIT(ctl, EventTriggerCacheEntry, event);
 	ctl.hcxt = EventTriggerCacheContext;
 	cache = hash_create("EventTriggerCacheHash", 32, &ctl,
 						HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c
index afc048a051e..cdb1b8477cd 100644
--- a/src/backend/utils/cache/funccache.c
+++ b/src/backend/utils/cache/funccache.c
@@ -63,8 +63,7 @@ cfunc_hashtable_init(void)
 	/* don't allow double-initialization */
 	Assert(cfunc_hashtable == NULL);
 
-	ctl.keysize = sizeof(CachedFunctionHashKey);
-	ctl.entrysize = sizeof(CachedFunctionHashEntry);
+	HASH_ELEM_INIT(ctl, CachedFunctionHashEntry, key);
 	ctl.hash = cfunc_hash;
 	ctl.match = cfunc_match;
 	cfunc_hashtable = hash_create("Cached function hash",
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..e366bff6a90 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1684,8 +1684,7 @@ LookupOpclassInfo(Oid operatorClassOid,
 		if (!CacheMemoryContext)
 			CreateCacheMemoryContext();
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(OpClassCacheEnt);
+		HASH_ELEM_INIT(ctl, OpClassCacheEnt, opclassoid);
 		OpClassCache = hash_create("Operator class cache", 64,
 								   &ctl, HASH_ELEM | HASH_BLOBS);
 	}
@@ -4013,8 +4012,7 @@ RelationCacheInitialize(void)
 	/*
 	 * create hashtable that indexes the relcache
 	 */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(RelIdCacheEnt);
+	HASH_ELEM_INIT(ctl, RelIdCacheEnt, reloid);
 	RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE,
 								  &ctl, HASH_ELEM | HASH_BLOBS);
 
diff --git a/src/backend/utils/cache/relfilenumbermap.c b/src/backend/utils/cache/relfilenumbermap.c
index 0b6f9cf3fa1..50568dc4139 100644
--- a/src/backend/utils/cache/relfilenumbermap.c
+++ b/src/backend/utils/cache/relfilenumbermap.c
@@ -113,8 +113,7 @@ InitializeRelfilenumberMap(void)
 	 * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
 	 * error.
 	 */
-	ctl.keysize = sizeof(RelfilenumberMapKey);
-	ctl.entrysize = sizeof(RelfilenumberMapEntry);
+	HASH_ELEM_INIT(ctl, RelfilenumberMapEntry, key);
 	ctl.hcxt = CacheMemoryContext;
 
 	RelfilenumberMapHash =
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index 23458599298..447e75787fd 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -80,8 +80,7 @@ InitializeTableSpaceCache(void)
 	HASHCTL		ctl;
 
 	/* Initialize the hash table. */
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(TableSpaceCacheEntry);
+	HASH_ELEM_INIT(ctl, TableSpaceCacheEntry, oid);
 	TableSpaceCacheHash =
 		hash_create("TableSpace cache", 16, &ctl,
 					HASH_ELEM | HASH_BLOBS);
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index e8ae53238d0..b10e75b3d49 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -119,8 +119,7 @@ lookup_ts_parser_cache(Oid prsId)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TSParserCacheEntry);
+		HASH_ELEM_INIT(ctl, TSParserCacheEntry, prsId);
 		TSParserCacheHash = hash_create("Tsearch parser cache", 4,
 										&ctl, HASH_ELEM | HASH_BLOBS);
 		/* Flush cache on pg_ts_parser changes */
@@ -214,8 +213,7 @@ lookup_ts_dictionary_cache(Oid dictId)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TSDictionaryCacheEntry);
+		HASH_ELEM_INIT(ctl, TSDictionaryCacheEntry, dictId);
 		TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
 											&ctl, HASH_ELEM | HASH_BLOBS);
 		/* Flush cache on pg_ts_dict and pg_ts_template changes */
@@ -365,8 +363,7 @@ init_ts_config_cache(void)
 {
 	HASHCTL		ctl;
 
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(TSConfigCacheEntry);
+	HASH_ELEM_INIT(ctl, TSConfigCacheEntry, cfgId);
 	TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
 									&ctl, HASH_ELEM | HASH_BLOBS);
 	/* Flush cache on pg_ts_config and pg_ts_config_map changes */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 6a347698edf..0318fa45783 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -395,8 +395,7 @@ lookup_type_cache(Oid type_id, int flags)
 		HASHCTL		ctl;
 		int			allocsize;
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(TypeCacheEntry);
+		HASH_ELEM_INIT(ctl, TypeCacheEntry, type_id);
 
 		/*
 		 * TypeCacheEntry takes hash value from the system cache. For
@@ -410,8 +409,7 @@ lookup_type_cache(Oid type_id, int flags)
 
 		Assert(RelIdToTypeIdCacheHash == NULL);
 
-		ctl.keysize = sizeof(Oid);
-		ctl.entrysize = sizeof(RelIdToTypeIdCacheEntry);
+		HASH_ELEM_INIT(ctl, RelIdToTypeIdCacheEntry, relid);
 		RelIdToTypeIdCacheHash = hash_create("Map from relid to OID of cached composite type", 64,
 											 &ctl, HASH_ELEM | HASH_BLOBS);
 
@@ -2052,8 +2050,7 @@ assign_record_type_typmod(TupleDesc tupDesc)
 		/* First time through: initialize the hash table */
 		HASHCTL		ctl;
 
-		ctl.keysize = sizeof(TupleDesc);	/* just the pointer */
-		ctl.entrysize = sizeof(RecordCacheEntry);
+		HASH_ELEM_INIT(ctl, RecordCacheEntry, tupdesc);
 		ctl.hash = record_type_typmod_hash;
 		ctl.match = record_type_typmod_compare;
 		RecordCacheHash = hash_create("Record information cache", 64,
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 1366521f471..1b0f6cdad17 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -673,8 +673,7 @@ find_rendezvous_variable(const char *varName)
 	{
 		HASHCTL		ctl;
 
-		ctl.keysize = NAMEDATALEN;
-		ctl.entrysize = sizeof(rendezvousHashEntry);
+		HASH_ELEM_INIT(ctl, rendezvousHashEntry, varName);
 		rendezvousHash = hash_create("Rendezvous variable hash",
 									 16,
 									 &ctl,
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..8cd1dfa1ae1 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -549,8 +549,7 @@ record_C_func(HeapTuple procedureTuple,
 	{
 		HASHCTL		hash_ctl;
 
-		hash_ctl.keysize = sizeof(Oid);
-		hash_ctl.entrysize = sizeof(CFuncHashTabEntry);
+		HASH_ELEM_INIT(hash_ctl, CFuncHashTabEntry, fn_oid);
 		CFuncHash = hash_create("CFuncHash",
 								100,
 								&hash_ctl,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c6484aea087..3d66f5ab1b9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -894,8 +894,7 @@ build_guc_variables(void)
 	 */
 	size_vars = num_vars + num_vars / 4;
 
-	hash_ctl.keysize = sizeof(char *);
-	hash_ctl.entrysize = sizeof(GUCHashEntry);
+	HASH_ELEM_INIT(hash_ctl, GUCHashEntry, gucname);
 	hash_ctl.hash = guc_name_hash;
 	hash_ctl.match = guc_name_match;
 	hash_ctl.hcxt = GUCMemoryContext;
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index d02618c7ffe..696d89cd125 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -129,8 +129,7 @@ injection_point_cache_add(const char *name,
 	{
 		HASHCTL		hash_ctl;
 
-		hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
-		hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
+		HASH_ELEM_INIT(hash_ctl, InjectionPointCacheEntry, name);
 		hash_ctl.hcxt = TopMemoryContext;
 
 		InjectionPointCache = hash_create("InjectionPoint cache hash",
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 943da087c9f..8f89ac94ddd 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -111,8 +111,8 @@ EnablePortalManager(void)
 											 "TopPortalContext",
 											 ALLOCSET_DEFAULT_SIZES);
 
-	ctl.keysize = MAX_PORTALNAME_LEN;
-	ctl.entrysize = sizeof(PortalHashEnt);
+	HASH_ELEM_INIT(ctl, PortalHashEnt, portalname);
+
 
 	/*
 	 * use PORTALS_PER_USER as a guess of how many hash table entries to
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 1e815571570..676191f517e 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -223,8 +223,7 @@ GetComboCommandId(CommandId cmin, CommandId cmax)
 		sizeComboCids = CCID_ARRAY_SIZE;
 		usedComboCids = 0;
 
-		hash_ctl.keysize = sizeof(ComboCidKeyData);
-		hash_ctl.entrysize = sizeof(ComboCidEntryData);
+		HASH_ELEM_INIT(hash_ctl, ComboCidEntryData, key);
 		hash_ctl.hcxt = TopTransactionContext;
 
 		comboHash = hash_create("Combo CIDs",
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index cb09a4cbe8c..8fb4ae82b26 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -107,6 +107,41 @@ typedef struct HASHCTL
 /* max_dsize value to indicate expansible directory */
 #define NO_MAX_DSIZE			(-1)
 
+/*
+ * Initialize hash table elements with type safety.
+ *
+ * This macro sets up the keysize and entrysize fields of a HASHCTL structure
+ * in a type-safe manner. It ensures:
+ *
+ * 1. The key member is at offset 0 in the entry structure
+ * 2. The key size is derived from the actual member type
+ * 3. The key member is explicitly referenced
+ *
+ * This replaces the error prone pattern:
+ *
+ *   ctl.keysize = sizeof(KeyType);
+ *   ctl.entrysize = sizeof(MyHashEntry);
+ */
+#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)
+
+/*
+ * Initialize hash table elements where the whole entry is the key.
+ *
+ * This macro is used for the special case where the hash table entry structure
+ * itself serves as the key.
+ */
+#define HASH_ELEM_INIT_FULL(ctl, entrytype)		\
+	do {										\
+		(ctl).keysize = sizeof(entrytype);		\
+		(ctl).entrysize = sizeof(entrytype);	\
+	} while (0)
+
 /* hash_search operations */
 typedef enum
 {
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 73ba1748fe0..320c9019cd2 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -460,15 +460,13 @@ _PG_init(void)
 	/*
 	 * Create hash tables.
 	 */
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(plperl_interp_desc);
+	HASH_ELEM_INIT(hash_ctl, plperl_interp_desc, user_id);
 	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);
+	HASH_ELEM_INIT(hash_ctl, plperl_proc_ptr, proc_key);
 	plperl_proc_hash = hash_create("PL/Perl procedures",
 								   32,
 								   &hash_ctl,
@@ -580,8 +578,7 @@ select_perl_context(bool trusted)
 	{
 		HASHCTL		hash_ctl;
 
-		hash_ctl.keysize = NAMEDATALEN;
-		hash_ctl.entrysize = sizeof(plperl_query_entry);
+		HASH_ELEM_INIT(hash_ctl, plperl_query_entry, query_name);
 		interp_desc->query_hash = hash_create("PL/Perl queries",
 											  32,
 											  &hash_ctl,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d19425b7a71..5185e8880ae 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4052,8 +4052,7 @@ 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);
+		HASH_ELEM_INIT(ctl, plpgsql_CastExprHashEntry, key);
 		cast_expr_hash = hash_create("PLpgSQL cast expressions",
 									 16,	/* start small and extend */
 									 &ctl,
@@ -4065,8 +4064,7 @@ 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);
+		HASH_ELEM_INIT(ctl, plpgsql_CastHashEntry, key);
 		ctl.hcxt = CurrentMemoryContext;
 		estate->cast_hash = hash_create("PLpgSQL private cast cache",
 										16, /* start small and extend */
@@ -4079,8 +4077,7 @@ 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);
+			HASH_ELEM_INIT(ctl, plpgsql_CastHashEntry, key);
 			shared_cast_hash = hash_create("PLpgSQL cast cache",
 										   16,	/* start small and extend */
 										   &ctl,
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 89931612c5b..3931f4cecc4 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -193,8 +193,7 @@ 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);
+	HASH_ELEM_INIT(hash_ctl, PLyExceptionEntry, sqlstate);
 	PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256,
 									 &hash_ctl, HASH_ELEM | HASH_BLOBS);
 
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 655ab1d09ee..075e24c027c 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -31,8 +31,7 @@ init_procedure_caches(void)
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = sizeof(PLyProcedureKey);
-	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
+	HASH_ELEM_INIT(hash_ctl, PLyProcedureEntry, key);
 	PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
 									  HASH_ELEM | HASH_BLOBS);
 }
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 73d660e88a6..963d6b0d0a0 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -446,8 +446,7 @@ _PG_init(void)
 	/************************************************************
 	 * Create the hash table for working interpreters
 	 ************************************************************/
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(pltcl_interp_desc);
+	HASH_ELEM_INIT(hash_ctl, pltcl_interp_desc, user_id);
 	pltcl_interp_htab = hash_create("PL/Tcl interpreters",
 									8,
 									&hash_ctl,
@@ -456,8 +455,7 @@ _PG_init(void)
 	/************************************************************
 	 * Create the hash table for function lookup
 	 ************************************************************/
-	hash_ctl.keysize = sizeof(pltcl_proc_key);
-	hash_ctl.entrysize = sizeof(pltcl_proc_ptr);
+	HASH_ELEM_INIT(hash_ctl, pltcl_proc_ptr, proc_key);
 	pltcl_proc_htab = hash_create("PL/Tcl functions",
 								  100,
 								  &hash_ctl,
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 504c0235ffb..24f411a6738 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -203,8 +203,7 @@ init_timezone_hashtable(void)
 {
 	HASHCTL		hash_ctl;
 
-	hash_ctl.keysize = TZ_STRLEN_MAX + 1;
-	hash_ctl.entrysize = sizeof(pg_tz_cache);
+	HASH_ELEM_INIT(hash_ctl, pg_tz_cache, tznameupper);
 
 	timezone_cache = hash_create("Timezones",
 								 4,
-- 
2.34.1

