v3-0001-Add-hash_make-macros.patch
text/x-patch
Filename: v3-0001-Add-hash_make-macros.patch
Type: text/x-patch
Part: 0
From 5288920fd80a612d5660fb576dcf80bb6461ab0e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Thu, 4 Dec 2025 15:35:08 +0100
Subject: [PATCH v3 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 <bertranddrouvot.pg@gmail.com>
---
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;
}
+/*
+ * 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)
+ {
+ /* force_blobs only affects default hash selection, not custom hash */
+ Assert(!opts->force_blobs);
+ ctl->hash = opts->hash;
+ *flags |= HASH_FUNCTION;
+ }
+ else if (opts != NULL && opts->force_blobs)
+ {
+ *flags |= HASH_BLOBS;
+ }
+ 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/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);
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..98c88726345 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,150 @@ 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 */
+ bool force_blobs; /* if true, use HASH_BLOBS even for string
+ * types */
+} HASHOPTS;
+
+/*
+ * Helpers to detect if a type should be hashed as a string.
+ *
+ * String types include: char arrays and NameData.
+ * Everything else is treated as a binary blob (HASH_BLOBS).
+ */
+#define HASH_PTR_AS_STRING(ptr, size) \
+ (pg_expr_has_type_p((ptr), char(*)[size]) || pg_expr_has_type_p((ptr), NameData *))
+#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(pg_nullptr_of(type), 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 +204,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 +278,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 6e2ed0c8825..73601910642 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1145,6 +1145,7 @@ HASHBUCKET
HASHCTL
HASHELEMENT
HASHHDR
+HASHOPTS
HASHSEGMENT
HASH_SEQ_STATUS
HE
base-commit: 31280d96a64850f5a9a924088890ab43a2905237
--
2.52.0