public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v6 2/3] Make resowners more easily extensible. 2+ messages / 2 participants [nested] [flat]
* [PATCH v6 2/3] Make resowners more easily extensible. @ 2021-01-20 21:16 Heikki Linnakangas <[email protected]> 0 siblings, 0 replies; 2+ messages in thread From: Heikki Linnakangas @ 2021-01-20 21:16 UTC (permalink / raw) Use a single array and hash, instead of one for each object kind. --- src/backend/access/common/tupdesc.c | 42 +- src/backend/jit/jit.c | 2 - src/backend/jit/llvm/llvmjit.c | 46 +- src/backend/storage/buffer/bufmgr.c | 45 +- src/backend/storage/buffer/localbuf.c | 4 +- src/backend/storage/file/fd.c | 44 +- src/backend/storage/ipc/dsm.c | 44 +- src/backend/storage/lmgr/lock.c | 2 +- src/backend/utils/cache/catcache.c | 74 +- src/backend/utils/cache/plancache.c | 45 +- src/backend/utils/cache/relcache.c | 41 +- src/backend/utils/resowner/README | 19 +- src/backend/utils/resowner/resowner.c | 1236 +++++++------------------ src/backend/utils/time/snapmgr.c | 38 +- src/common/cryptohash_openssl.c | 49 +- src/include/storage/buf_internals.h | 9 + src/include/utils/catcache.h | 3 - src/include/utils/plancache.h | 2 + src/include/utils/resowner.h | 45 +- src/include/utils/resowner_private.h | 105 --- src/tools/pgindent/typedefs.list | 4 +- 21 files changed, 844 insertions(+), 1055 deletions(-) delete mode 100644 src/include/utils/resowner_private.h diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 902f59440cd..2bf0c121fda 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -29,9 +29,27 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/datum.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/syscache.h" +/* ResourceOwner callbacks to hold tupledesc references */ +static void ResOwnerReleaseTupleDesc(Datum res); +static void ResOwnerPrintTupleDescLeakWarning(Datum res); + +static ResourceOwnerFuncs tupdesc_resowner_funcs = +{ + /* relcache references */ + .name = "tupdesc reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseTupleDesc, + .PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberTupleDesc(owner, tupdesc) \ + ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs) +#define ResourceOwnerForgetTupleDesc(owner, tupdesc) \ + ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs) /* * CreateTemplateTupleDesc @@ -376,7 +394,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc) { Assert(tupdesc->tdrefcount >= 0); - ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); tupdesc->tdrefcount++; ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc); } @@ -925,3 +943,23 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations) return desc; } + + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseTupleDesc(Datum res) +{ + DecrTupleDescRefCount((TupleDesc) DatumGetPointer(res)); +} + +static void +ResOwnerPrintTupleDescLeakWarning(Datum res) +{ + TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res); + + elog(WARNING, + "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced", + tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); +} diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c index 2da300e000d..1aa04d173b4 100644 --- a/src/backend/jit/jit.c +++ b/src/backend/jit/jit.c @@ -26,7 +26,6 @@ #include "jit/jit.h" #include "miscadmin.h" #include "utils/fmgrprotos.h" -#include "utils/resowner_private.h" /* GUCs */ bool jit_enabled = true; @@ -140,7 +139,6 @@ jit_release_context(JitContext *context) if (provider_successfully_loaded) provider.release_context(context); - ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context)); pfree(context); } diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index b0789a5fb80..8e5b86a0e5b 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -40,7 +40,7 @@ #include "portability/instr_time.h" #include "storage/ipc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* Handle of a module emitted via ORC JIT */ typedef struct LLVMJitHandle @@ -121,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm); static char *llvm_error_message(LLVMErrorRef error); #endif /* LLVM_VERSION_MAJOR > 11 */ -PG_MODULE_MAGIC; +/* ResourceOwner callbacks to hold JitContexts */ +static void ResOwnerReleaseJitContext(Datum res); +static void ResOwnerPrintJitContextLeakWarning(Datum res); + +static ResourceOwnerFuncs jit_resowner_funcs = +{ + /* relcache references */ + .name = "LLVM JIT context", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseJitContext, + .PrintLeakWarning = ResOwnerPrintJitContextLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberJIT(owner, handle) \ + ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_funcs) +#define ResourceOwnerForgetJIT(owner, handle) \ + ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_funcs) +PG_MODULE_MAGIC; /* * Initialize LLVM JIT provider. @@ -151,7 +169,7 @@ llvm_create_context(int jitFlags) llvm_session_initialize(); - ResourceOwnerEnlargeJIT(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); context = MemoryContextAllocZero(TopMemoryContext, sizeof(LLVMJitContext)); @@ -159,7 +177,7 @@ llvm_create_context(int jitFlags) /* ensure cleanup */ context->base.resowner = CurrentResourceOwner; - ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context)); + ResourceOwnerRememberJIT(CurrentResourceOwner, context); return context; } @@ -221,6 +239,8 @@ llvm_release_context(JitContext *context) pfree(jit_handle); } + + ResourceOwnerForgetJIT(context->resowner, context); } /* @@ -1231,3 +1251,21 @@ llvm_error_message(LLVMErrorRef error) } #endif /* LLVM_VERSION_MAJOR > 11 */ + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseJitContext(Datum res) +{ + jit_release_context((JitContext *) PointerGetDatum(res)); +} + +static void +ResOwnerPrintJitContextLeakWarning(Datum res) +{ + /* XXX: We used to not print these. Was that intentional? */ + JitContext *context = (JitContext *) PointerGetDatum(res); + + elog(WARNING, "JIT context leak: context %p still referenced", context); +} diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index cde869e7d64..82e39de19f9 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -52,7 +52,7 @@ #include "utils/memdebug.h" #include "utils/ps_status.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/timestamp.h" @@ -206,6 +206,18 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move static inline int32 GetPrivateRefCount(Buffer buffer); static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref); +/* ResourceOwner callbacks to hold buffer pins */ +static void ResOwnerReleaseBuffer(Datum res); +static void ResOwnerPrintBufferLeakWarning(Datum res); + +ResourceOwnerFuncs buffer_resowner_funcs = +{ + .name = "buffer", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseBuffer, + .PrintLeakWarning = ResOwnerPrintBufferLeakWarning +}; + /* * Ensure that the PrivateRefCountArray has sufficient space to store one more * entry. This has to be called before using NewPrivateRefCountEntry() to fill @@ -1094,7 +1106,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, * pin. */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Select a victim buffer. The buffer is returned with its header @@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) bool result; PrivateRefCountEntry *ref; - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ref = GetPrivateRefCountEntry(b, true); @@ -1687,7 +1699,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) * * As this function is called with the spinlock held, the caller has to * previously call ReservePrivateRefCountEntry() and - * ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + * ResourceOwnerEnlarge(CurrentResourceOwner); * * Currently, no callers of this function want to modify the buffer's * usage_count at all, so there's no need for a strategy parameter. @@ -2423,7 +2435,7 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Check whether buffer needs writing. @@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) && @@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) && @@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (bufHdr->tag.rnode.dbNode == dbid && @@ -3729,7 +3741,7 @@ void IncrBufferRefCount(Buffer buffer) { Assert(BufferIsPinned(buffer)); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); if (BufferIsLocal(buffer)) LocalRefCount[-buffer - 1]++; else @@ -4803,3 +4815,18 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation) (errcode(ERRCODE_SNAPSHOT_TOO_OLD), errmsg("snapshot too old"))); } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseBuffer(Datum res) +{ + ReleaseBuffer(DatumGetInt32(res)); +} + +static void +ResOwnerPrintBufferLeakWarning(Datum res) +{ + PrintBufferLeakWarning(DatumGetInt32(res)); +} diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c index f7c15ea8a44..3f22d7127b9 100644 --- a/src/backend/storage/buffer/localbuf.c +++ b/src/backend/storage/buffer/localbuf.c @@ -22,7 +22,7 @@ #include "storage/bufmgr.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /*#define LBDEBUG*/ @@ -124,7 +124,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, InitLocalBuffers(); /* Make sure we will have room to remember the buffer pin */ - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* See if the desired buffer already exists */ hresult = (LocalBufferLookupEnt *) diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index b58502837aa..1172ea245e0 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -97,7 +97,7 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "utils/guc.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */ #if defined(HAVE_SYNC_FILE_RANGE) @@ -340,6 +340,24 @@ static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel); static int fsync_parent_path(const char *fname, int elevel); +/* ResourceOwner callbacks to hold virtual file descriptors */ +static void ResOwnerReleaseFile(Datum res); +static void ResOwnerPrintFileLeakWarning(Datum res); + +static ResourceOwnerFuncs file_resowner_funcs = +{ + .name = "File", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseFile, + .PrintLeakWarning = ResOwnerPrintFileLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberFile(owner, file) \ + ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_funcs) +#define ResourceOwnerForgetFile(owner, file) \ + ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_funcs) + /* * pg_fsync --- do fsync with or without writethrough */ @@ -1430,7 +1448,7 @@ ReportTemporaryFileUsage(const char *path, off_t size) /* * Called to register a temporary file for automatic close. - * ResourceOwnerEnlargeFiles(CurrentResourceOwner) must have been called + * ResourceOwnerEnlarge(CurrentResourceOwner) must have been called * before the file was opened. */ static void @@ -1612,7 +1630,7 @@ OpenTemporaryFile(bool interXact) * open it, if we'll be registering it below. */ if (!interXact) - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * If some temp tablespace(s) have been given to us, try to use the next @@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure) { File file; - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Open the file. Note: we don't use O_EXCL, in case there is an orphaned @@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode) { File file; - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); file = PathNameOpenFile(path, mode | PG_BINARY); @@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset) return sum; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseFile(Datum res) +{ + FileClose((File) DatumGetInt32(res)); +} + +static void +ResOwnerPrintFileLeakWarning(Datum res) +{ + elog(WARNING, "temporary file leak: File %d still referenced", + DatumGetInt32(res)); +} diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index ae82b4bdc0e..31e8860fb53 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -37,13 +37,15 @@ #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/dsm.h" +#include "storage/fd.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/pg_shmem.h" +#include "storage/shmem.h" #include "utils/freepage.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #define PG_DYNSHMEM_CONTROL_MAGIC 0x9a503d32 @@ -139,6 +141,25 @@ static dsm_control_header *dsm_control; static Size dsm_control_mapped_size = 0; static void *dsm_control_impl_private = NULL; + +/* ResourceOwner callbacks to hold DSM segments */ +static void ResOwnerReleaseDSM(Datum res); +static void ResOwnerPrintDSMLeakWarning(Datum res); + +static ResourceOwnerFuncs dsm_resowner_funcs = +{ + .name = "dynamic shared memory segment", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseDSM, + .PrintLeakWarning = ResOwnerPrintDSMLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberDSM(owner, seg) \ + ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_funcs) +#define ResourceOwnerForgetDSM(owner, seg) \ + ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_funcs) + /* * Start up the dynamic shared memory system. * @@ -895,7 +916,7 @@ void dsm_unpin_mapping(dsm_segment *seg) { Assert(seg->resowner == NULL); - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg->resowner = CurrentResourceOwner; ResourceOwnerRememberDSM(seg->resowner, seg); } @@ -1162,7 +1183,7 @@ dsm_create_descriptor(void) dsm_segment *seg; if (CurrentResourceOwner) - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment)); dlist_push_head(&dsm_segment_list, &seg->node); @@ -1241,3 +1262,20 @@ is_main_region_dsm_handle(dsm_handle handle) { return handle & 1; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseDSM(Datum res) +{ + dsm_detach((dsm_segment *) DatumGetPointer(res)); +} +static void +ResOwnerPrintDSMLeakWarning(Datum res) +{ + dsm_segment *seg = (dsm_segment *) res; + + elog(WARNING, "dynamic shared memory leak: segment %u still referenced", + dsm_segment_handle(seg)); +} diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 20e50247ea4..de39c844d49 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -47,7 +47,7 @@ #include "storage/standby.h" #include "utils/memutils.h" #include "utils/ps_status.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* This configuration variable is used to set the lock table size */ diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 86fdfc454d2..9f9db426274 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -31,12 +31,13 @@ #endif #include "storage/lmgr.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/syscache.h" @@ -104,6 +105,42 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, * internal support functions */ +/* ResourceOwner callbacks to hold catcache references */ + +static void ResOwnerReleaseCatCache(Datum res); +static void ResOwnerPrintCatCacheLeakWarning(Datum res); +static void ResOwnerReleaseCatCacheList(Datum res); +static void ResOwnerPrintCatCacheListLeakWarning(Datum res); + +static ResourceOwnerFuncs catcache_resowner_funcs = +{ + /* catcache references */ + .name = "catcache reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCatCache, + .PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning +}; + +static ResourceOwnerFuncs catlistref_resowner_funcs = +{ + /* catcache-list pins */ + .name = "catcache list reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCatCacheList, + .PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberCatCacheRef(owner, tuple) \ + ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_funcs) +#define ResourceOwnerForgetCatCacheRef(owner, tuple) \ + ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_funcs) +#define ResourceOwnerRememberCatCacheListRef(owner, list) \ + ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_funcs) +#define ResourceOwnerForgetCatCacheListRef(owner, list) \ + ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_funcs) + + /* * Hash and equality functions for system types that are used as cache key * fields. In some cases, we just call the regular SQL-callable functions for @@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache, */ if (!ct->negative) { - ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ct->refcount++; ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); @@ -1371,7 +1408,7 @@ SearchCatCacheMiss(CatCache *cache, hashValue, hashIndex, false); /* immediately set the refcount to 1 */ - ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ct->refcount++; ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); break; /* assume only one match */ @@ -1583,7 +1620,7 @@ SearchCatCacheList(CatCache *cache, dlist_move_head(&cache->cc_lists, &cl->cache_elem); /* Bump the list's refcount and return it */ - ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); cl->refcount++; ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); @@ -1695,7 +1732,7 @@ SearchCatCacheList(CatCache *cache, table_close(relation, AccessShareLock); /* Make sure the resource owner has room to remember this entry. */ - ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* Now we can build the CatCList entry. */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); @@ -2069,14 +2106,19 @@ PrepareToInvalidateCacheTuple(Relation relation, } } - /* - * Subroutines for warning about reference leaks. These are exported so - * that resowner.c can call them. + * ResourceOwner callbacks */ -void -PrintCatCacheLeakWarning(HeapTuple tuple) +static void +ResOwnerReleaseCatCache(Datum res) +{ + ReleaseCatCache((HeapTuple) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCatCacheLeakWarning(Datum res) { + HeapTuple tuple = (HeapTuple) DatumGetPointer(res); CatCTup *ct = (CatCTup *) (((char *) tuple) - offsetof(CatCTup, tuple)); @@ -2090,9 +2132,17 @@ PrintCatCacheLeakWarning(HeapTuple tuple) ct->refcount); } -void -PrintCatCacheListLeakWarning(CatCList *list) +static void +ResOwnerReleaseCatCacheList(Datum res) { + ReleaseCatCacheList((CatCList *) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCatCacheListLeakWarning(Datum res) +{ + CatCList *list = (CatCList *) DatumGetPointer(res); + elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d", list->my_cache->cc_relname, list->my_cache->id, list, list->refcount); diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index cc04b5b4bef..ecc9296d2d3 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -69,7 +69,7 @@ #include "tcop/utility.h" #include "utils/inval.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -115,6 +115,26 @@ static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); +/* ResourceOwner callbacks to track plancache references */ +static void ResOwnerReleaseCachedPlan(Datum res); +static void ResOwnerPrintPlanCacheLeakWarning(Datum res); + +/* this is exported for ResourceOwnerReleaseAllPlanCacheRefs() */ +ResourceOwnerFuncs planref_resowner_funcs = +{ + .name = "plancache reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCachedPlan, + .PrintLeakWarning = ResOwnerPrintPlanCacheLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberPlanCacheRef(owner, plan) \ + ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs) +#define ResourceOwnerForgetPlanCacheRef(owner, plan) \ + ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs) + + /* GUC parameter */ int plan_cache_mode; @@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, /* Flag the plan as in use by caller */ if (useResOwner) - ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); plan->refcount++; if (useResOwner) ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan); @@ -1392,7 +1412,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, /* Bump refcount if requested. */ if (owner) { - ResourceOwnerEnlargePlanCacheRefs(owner); + ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } @@ -1451,7 +1471,7 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan, /* It's still good. Bump refcount if requested. */ if (owner) { - ResourceOwnerEnlargePlanCacheRefs(owner); + ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } @@ -2205,3 +2225,20 @@ ResetPlanCache(void) cexpr->is_valid = false; } } + +/* + * ResourceOwner callbacks + */ + +static void +ResOwnerReleaseCachedPlan(Datum res) +{ + ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true); +} + +static void +ResOwnerPrintPlanCacheLeakWarning(Datum res) +{ + elog(WARNING, "plancache reference leak: plan %p not closed", + DatumGetPointer(res)); +} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7ef510cd01b..6ca5b50552b 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -78,13 +78,14 @@ #include "storage/smgr.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/relmapper.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -2078,6 +2079,24 @@ RelationIdGetRelation(Oid relationId) * ---------------------------------------------------------------- */ +/* ResourceOwner callbacks to track relcache references */ +static void ResOwnerReleaseRelation(Datum res); +static void ResOwnerPrintRelCacheLeakWarning(Datum res); + +static ResourceOwnerFuncs relref_resowner_funcs = +{ + .name = "relcache reference", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseRelation, + .PrintLeakWarning = ResOwnerPrintRelCacheLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberRelationRef(owner, rel) \ + ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_funcs) +#define ResourceOwnerForgetRelationRef(owner, rel) \ + ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_funcs) + /* * RelationIncrementReferenceCount * Increments relation reference count. @@ -2089,7 +2108,7 @@ RelationIdGetRelation(Oid relationId) void RelationIncrementReferenceCount(Relation rel) { - ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); rel->rd_refcnt += 1; if (!IsBootstrapProcessingMode()) ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel); @@ -6417,3 +6436,21 @@ unlink_initfile(const char *initfilename, int elevel) initfilename))); } } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerPrintRelCacheLeakWarning(Datum res) +{ + Relation rel = (Relation) res; + + elog(WARNING, "relcache reference leak: relation \"%s\" not closed", + RelationGetRelationName(rel)); +} + +static void +ResOwnerReleaseRelation(Datum res) +{ + RelationClose((Relation) res); +} diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README index 2998f6bb362..4ed13afa101 100644 --- a/src/backend/utils/resowner/README +++ b/src/backend/utils/resowner/README @@ -60,13 +60,18 @@ subtransaction or portal. Therefore, the "release" operation on a child ResourceOwner transfers lock ownership to the parent instead of actually releasing the lock, if isCommit is true. -Currently, ResourceOwners contain direct support for recording ownership of -buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and -snapshot references. Other objects can be associated with a ResourceOwner by -recording the address of the owning ResourceOwner in such an object. There is -an API for other modules to get control during ResourceOwner release, so that -they can scan their own data structures to find the objects that need to be -deleted. +ResourceOwner can record ownership of many different kinds of resources. +As of this writing, it's used internally for buffer pins, lmgr locks, and +catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context +references. ResourceOwner treats all resources the same, and extensions +can define new kinds of resources by filling in a ResourceOwnerFuncs +struct with a suitable callback functions. + +There is also an API for other modules to get control during ResourceOwner +release, so that they can scan their own data structures to find the objects +that need to be deleted. This used to be the only way to register new kinds +of objects with a resource owner; nowadays it easier to write custom +ResourceOwnerFuncs callbacks. Whenever we are inside a transaction, the global variable CurrentResourceOwner shows which resource owner should be assigned diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 10f15f6a357..1db1e7cf14b 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -20,75 +20,45 @@ */ #include "postgres.h" -#include "common/cryptohash.h" #include "common/hashfn.h" -#include "jit/jit.h" -#include "storage/bufmgr.h" #include "storage/ipc.h" #include "storage/predicate.h" #include "storage/proc.h" #include "utils/memutils.h" -#include "utils/rel.h" -#include "utils/resowner_private.h" -#include "utils/snapmgr.h" - - -/* - * All resource IDs managed by this code are required to fit into a Datum, - * which is fine since they are generally pointers or integers. - * - * Provide Datum conversion macros for a couple of things that are really - * just "int". - */ -#define FileGetDatum(file) Int32GetDatum(file) -#define DatumGetFile(datum) ((File) DatumGetInt32(datum)) -#define BufferGetDatum(buffer) Int32GetDatum(buffer) -#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum)) +#include "utils/plancache.h" +#include "utils/resowner.h" /* - * ResourceArray is a common structure for storing all types of resource IDs. - * - * We manage small sets of resource IDs by keeping them in a simple array: - * itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity. - * - * If a set grows large, we switch over to using open-addressing hashing. - * Then, itemsarr[] is a hash table of "capacity" slots, with each - * slot holding either an ID or "invalidval". nitems is the number of valid - * items present; if it would exceed maxitems, we enlarge the array and - * re-hash. In this mode, maxitems should be rather less than capacity so - * that we don't waste too much time searching for empty slots. + * ResourceElem represents a reference associated with a resource owner. * - * In either mode, lastidx remembers the location of the last item inserted - * or returned by GetAny; this speeds up searches in ResourceArrayRemove. + * All objects managed by this code are required to fit into a Datum, + * which is fine since they are generally pointers or integers. */ -typedef struct ResourceArray +typedef struct ResourceElem { - Datum *itemsarr; /* buffer for storing values */ - Datum invalidval; /* value that is considered invalid */ - uint32 capacity; /* allocated length of itemsarr[] */ - uint32 nitems; /* how many items are stored in items array */ - uint32 maxitems; /* current limit on nitems before enlarging */ - uint32 lastidx; /* index of last item returned by GetAny */ -} ResourceArray; + Datum item; + ResourceOwnerFuncs *kind; +} ResourceElem; /* - * Initially allocated size of a ResourceArray. Must be power of two since - * we'll use (arraysize - 1) as mask for hashing. + * Size of the small fixed-size array to hold most-recently remembered resources. */ -#define RESARRAY_INIT_SIZE 16 +#define RESOWNER_ARRAY_SIZE 8 /* - * When to switch to hashing vs. simple array logic in a ResourceArray. + * Initially allocated size of a ResourceOwner's hash. Must be power of two since + * we'll use (capacity - 1) as mask for hashing. */ -#define RESARRAY_MAX_ARRAY 64 -#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY) +#define RESOWNER_HASH_INIT_SIZE 32 /* - * How many items may be stored in a resource array of given capacity. + * How many items may be stored in a hash of given capacity. * When this number is reached, we must resize. */ -#define RESARRAY_MAX_ITEMS(capacity) \ - ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3) +#define RESOWNER_HASH_MAX_ITEMS(capacity) ((capacity)/4 * 3) + +StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) > RESOWNER_ARRAY_SIZE, + "initial hash size too small compared to array size"); /* * To speed up bulk releasing or reassigning locks from a resource owner to @@ -118,23 +88,33 @@ typedef struct ResourceOwnerData ResourceOwner nextchild; /* next child of same parent */ const char *name; /* name (just for debugging) */ - /* We have built-in support for remembering: */ - ResourceArray bufferarr; /* owned buffers */ - ResourceArray catrefarr; /* catcache references */ - ResourceArray catlistrefarr; /* catcache-list pins */ - ResourceArray relrefarr; /* relcache references */ - ResourceArray planrefarr; /* plancache references */ - ResourceArray tupdescarr; /* tupdesc references */ - ResourceArray snapshotarr; /* snapshot references */ - ResourceArray filearr; /* open temporary files */ - ResourceArray dsmarr; /* dynamic shmem segments */ - ResourceArray jitarr; /* JIT contexts */ - ResourceArray cryptohasharr; /* cryptohash contexts */ + /* + * These structs keep track of the objects registered with this owner. + * + * We manage a small set of references by keeping them in a simple array. + * When the array gets full, all the elements in the array are moved to a + * hash table. This way, the array always contains a few most recently + * remembered references. To find a particular reference, you need to + * search both the array and the hash table. + */ + ResourceElem arr[RESOWNER_ARRAY_SIZE]; + uint32 narr; /* how many items are stored in the array */ + + /* + * The hash table. Uses open-addressing. 'nhash' is the number of items + * present; if it would exceed 'grow_at', we enlarge it and re-hash. + * 'grow_at' should be rather less than 'capacity' so that we don't waste + * too much time searching for empty slots. + */ + ResourceElem *hash; + uint32 nhash; /* how many items are stored in the hash */ + uint32 capacity; /* allocated length of hash[] */ + uint32 grow_at; /* grow hash when reach this */ /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */ int nlocks; /* number of owned locks */ LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */ -} ResourceOwnerData; +} ResourceOwnerData; /***************************************************************************** @@ -146,6 +126,18 @@ ResourceOwner CurTransactionResourceOwner = NULL; ResourceOwner TopTransactionResourceOwner = NULL; ResourceOwner AuxProcessResourceOwner = NULL; +/* #define RESOWNER_STATS */ +/* #define RESOWNER_TRACE */ + +#ifdef RESOWNER_STATS +static int narray_lookups = 0; +static int nhash_lookups = 0; +#endif + +#ifdef RESOWNER_TRACE +static int resowner_trace_counter = 0; +#endif + /* * List of add-on callbacks for resource releasing */ @@ -160,296 +152,315 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL; /* Internal routines */ -static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval); -static void ResourceArrayEnlarge(ResourceArray *resarr); -static void ResourceArrayAdd(ResourceArray *resarr, Datum value); -static bool ResourceArrayRemove(ResourceArray *resarr, Datum value); -static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value); -static void ResourceArrayFree(ResourceArray *resarr); static void ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); static void ReleaseAuxProcessResourcesCallback(int code, Datum arg); -static void PrintRelCacheLeakWarning(Relation rel); -static void PrintPlanCacheLeakWarning(CachedPlan *plan); -static void PrintTupleDescLeakWarning(TupleDesc tupdesc); -static void PrintSnapshotLeakWarning(Snapshot snapshot); -static void PrintFileLeakWarning(File file); -static void PrintDSMLeakWarning(dsm_segment *seg); -static void PrintCryptoHashLeakWarning(Datum handle); /***************************************************************************** * INTERNAL ROUTINES * *****************************************************************************/ +static inline uint32 +hash_resource_elem(Datum value, ResourceOwnerFuncs *kind) +{ + Datum data[2]; + + data[0] = value; + data[1] = PointerGetDatum(kind); + + return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM); +} -/* - * Initialize a ResourceArray - */ static void -ResourceArrayInit(ResourceArray *resarr, Datum invalidval) +ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind) { - /* Assert it's empty */ - Assert(resarr->itemsarr == NULL); - Assert(resarr->capacity == 0); - Assert(resarr->nitems == 0); - Assert(resarr->maxitems == 0); - /* Remember the appropriate "invalid" value */ - resarr->invalidval = invalidval; - /* We don't allocate any storage until needed */ + /* Insert into first free slot at or after hash location. */ + uint32 mask = owner->capacity - 1; + uint32 idx; + + idx = hash_resource_elem(value, kind) & mask; + for (;;) + { + if (owner->hash[idx].kind == NULL) + break; + idx = (idx + 1) & mask; + } + owner->hash[idx].item = value; + owner->hash[idx].kind = kind; + owner->nhash++; } /* - * Make sure there is room for at least one more resource in an array. - * - * This is separate from actually inserting a resource because if we run out - * of memory, it's critical to do so *before* acquiring the resource. + * Call the ReleaseResource callback on entries with given 'phase'. */ static void -ResourceArrayEnlarge(ResourceArray *resarr) +ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase, + bool printLeakWarnings) { - uint32 i, - oldcap, - newcap; - Datum *olditemsarr; - Datum *newitemsarr; - - if (resarr->nitems < resarr->maxitems) - return; /* no work needed */ + bool found; + int capacity; - olditemsarr = resarr->itemsarr; - oldcap = resarr->capacity; - - /* Double the capacity of the array (capacity must stay a power of 2!) */ - newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE; - newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext, - newcap * sizeof(Datum)); - for (i = 0; i < newcap; i++) - newitemsarr[i] = resarr->invalidval; + /* First handle all the entries in the array. */ + do + { + found = false; + for (int i = 0; i < owner->narr; i++) + { + if (owner->arr[i].kind->phase == phase) + { + Datum value = owner->arr[i].item; + ResourceOwnerFuncs *kind = owner->arr[i].kind; - /* We assume we can't fail below this point, so OK to scribble on resarr */ - resarr->itemsarr = newitemsarr; - resarr->capacity = newcap; - resarr->maxitems = RESARRAY_MAX_ITEMS(newcap); - resarr->nitems = 0; + if (printLeakWarnings) + kind->PrintLeakWarning(value); + kind->ReleaseResource(value); + found = true; + } + } - if (olditemsarr != NULL) - { /* - * Transfer any pre-existing entries into the new array; they don't - * necessarily go where they were before, so this simple logic is the - * best way. Note that if we were managing the set as a simple array, - * the entries after nitems are garbage, but that shouldn't matter - * because we won't get here unless nitems was equal to oldcap. + * If any resources were released, check again because some of the + * elements might have been moved by the callbacks. We don't want to + * miss them. */ - for (i = 0; i < oldcap; i++) + } while (found && owner->narr > 0); + + /* Ok, the array has now been handled. Then the hash */ + do + { + capacity = owner->capacity; + for (int idx = 0; idx < capacity; idx++) { - if (olditemsarr[i] != resarr->invalidval) - ResourceArrayAdd(resarr, olditemsarr[i]); + while (owner->hash[idx].kind != NULL && + owner->hash[idx].kind->phase == phase) + { + Datum value = owner->hash[idx].item; + ResourceOwnerFuncs *kind = owner->hash[idx].kind; + + if (printLeakWarnings) + kind->PrintLeakWarning(value); + kind->ReleaseResource(value); + + /* + * If the resource is remembered more than once in this + * resource owner, the ReleaseResource callback might've + * released a different copy of it. Because of that, loop to + * check the same index again. + */ + } } - /* And release old array. */ - pfree(olditemsarr); - } - - Assert(resarr->nitems < resarr->maxitems); + /* + * It's possible that the callbacks acquired more resources, causing + * the hash table to grow and the existing entries to be moved around. + * If that happened, scan the hash table again, so that we don't miss + * entries that were moved. (XXX: I'm not sure if any of the callbacks + * actually do that, but this is cheap to check, and better safe than + * sorry.) + */ + Assert(owner->capacity >= capacity); + } while (capacity != owner->capacity); } + +/***************************************************************************** + * EXPORTED ROUTINES * + *****************************************************************************/ + + /* - * Add a resource to ResourceArray + * ResourceOwnerCreate + * Create an empty ResourceOwner. * - * Caller must have previously done ResourceArrayEnlarge() + * All ResourceOwner objects are kept in TopMemoryContext, since they should + * only be freed explicitly. */ -static void -ResourceArrayAdd(ResourceArray *resarr, Datum value) +ResourceOwner +ResourceOwnerCreate(ResourceOwner parent, const char *name) { - uint32 idx; + ResourceOwner owner; - Assert(value != resarr->invalidval); - Assert(resarr->nitems < resarr->maxitems); + owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext, + sizeof(ResourceOwnerData)); + owner->name = name; - if (RESARRAY_IS_ARRAY(resarr)) + if (parent) { - /* Append to linear array. */ - idx = resarr->nitems; + owner->parent = parent; + owner->nextchild = parent->firstchild; + parent->firstchild = owner; } - else - { - /* Insert into first free slot at or after hash location. */ - uint32 mask = resarr->capacity - 1; - idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; - for (;;) - { - if (resarr->itemsarr[idx] == resarr->invalidval) - break; - idx = (idx + 1) & mask; - } - } - resarr->lastidx = idx; - resarr->itemsarr[idx] = value; - resarr->nitems++; +#ifdef RESOWNER_TRACE + elog(LOG, "CREATE %d: %p %s", + resowner_trace_counter++, owner, name); +#endif + + return owner; } /* - * Remove a resource from ResourceArray - * - * Returns true on success, false if resource was not found. + * Make sure there is room for at least one more resource in an array. * - * Note: if same resource ID appears more than once, one instance is removed. + * This is separate from actually inserting a resource because if we run out + * of memory, it's critical to do so *before* acquiring the resource. */ -static bool -ResourceArrayRemove(ResourceArray *resarr, Datum value) +void +ResourceOwnerEnlarge(ResourceOwner owner) { - uint32 i, - idx, - lastidx = resarr->lastidx; - - Assert(value != resarr->invalidval); + if (owner->narr < RESOWNER_ARRAY_SIZE) + return; /* no work needed */ - /* Search through all items, but try lastidx first. */ - if (RESARRAY_IS_ARRAY(resarr)) + /* Is there space in the hash? If not, enlarge it. */ + if (owner->narr + owner->nhash >= owner->grow_at) { - if (lastidx < resarr->nitems && - resarr->itemsarr[lastidx] == value) - { - resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1]; - resarr->nitems--; - /* Update lastidx to make reverse-order removals fast. */ - resarr->lastidx = resarr->nitems - 1; - return true; - } - for (i = 0; i < resarr->nitems; i++) + uint32 i, + oldcap, + newcap; + ResourceElem *oldhash; + ResourceElem *newhash; + + oldhash = owner->hash; + oldcap = owner->capacity; + + /* Double the capacity (it must stay a power of 2!) */ + newcap = (oldcap > 0) ? oldcap * 2 : RESOWNER_HASH_INIT_SIZE; + newhash = (ResourceElem *) MemoryContextAllocZero(TopMemoryContext, + newcap * sizeof(ResourceElem)); + + /* + * We assume we can't fail below this point, so OK to scribble on the + * owner + */ + owner->hash = newhash; + owner->capacity = newcap; + owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap); + owner->nhash = 0; + + if (oldhash != NULL) { - if (resarr->itemsarr[i] == value) + /* + * Transfer any pre-existing entries into the new hash table; they + * don't necessarily go where they were before, so this simple + * logic is the best way. + */ + for (i = 0; i < oldcap; i++) { - resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1]; - resarr->nitems--; - /* Update lastidx to make reverse-order removals fast. */ - resarr->lastidx = resarr->nitems - 1; - return true; + if (oldhash[i].kind != NULL) + ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind); } + + /* And release old hash table. */ + pfree(oldhash); } } - else - { - uint32 mask = resarr->capacity - 1; - if (lastidx < resarr->capacity && - resarr->itemsarr[lastidx] == value) - { - resarr->itemsarr[lastidx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; - for (i = 0; i < resarr->capacity; i++) - { - if (resarr->itemsarr[idx] == value) - { - resarr->itemsarr[idx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = (idx + 1) & mask; - } + /* Move items from the array to the hash */ + Assert(owner->narr == RESOWNER_ARRAY_SIZE); + for (int i = 0; i < owner->narr; i++) + { + ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind); } + owner->narr = 0; - return false; + Assert(owner->nhash < owner->grow_at); } /* - * Get any convenient entry in a ResourceArray. + * Remember that an object is owner by a ReourceOwner * - * "Convenient" is defined as "easy for ResourceArrayRemove to remove"; - * we help that along by setting lastidx to match. This avoids O(N^2) cost - * when removing all ResourceArray items during ResourceOwner destruction. - * - * Returns true if we found an element, or false if the array is empty. + * Caller must have previously done ResourceOwnerEnlarge() */ -static bool -ResourceArrayGetAny(ResourceArray *resarr, Datum *value) +void +ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind) { - if (resarr->nitems == 0) - return false; + uint32 idx; - if (RESARRAY_IS_ARRAY(resarr)) - { - /* Linear array: just return the first element. */ - resarr->lastidx = 0; - } - else - { - /* Hash: search forward from wherever we were last. */ - uint32 mask = resarr->capacity - 1; +#ifdef RESOWNER_TRACE + elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s", + resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name); +#endif - for (;;) - { - resarr->lastidx &= mask; - if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval) - break; - resarr->lastidx++; - } - } + Assert(owner->narr < RESOWNER_ARRAY_SIZE); - *value = resarr->itemsarr[resarr->lastidx]; - return true; + /* Append to linear array. */ + idx = owner->narr; + owner->arr[idx].item = value; + owner->arr[idx].kind = kind; + owner->narr++; } /* - * Trash a ResourceArray (we don't care about its state after this) + * Forget that an object is owned by a ResourceOwner + * + * Returns true on success. If the resource was not found, returns false, + * and calls kind->ForgetError callback. + * + * Note: if same resource ID is associated with the ResourceOwner more than once, + * one instance is removed. */ -static void -ResourceArrayFree(ResourceArray *resarr) +void +ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind) { - if (resarr->itemsarr) - pfree(resarr->itemsarr); -} - + uint32 i, + idx; -/***************************************************************************** - * EXPORTED ROUTINES * - *****************************************************************************/ +#ifdef RESOWNER_TRACE + elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s", + resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name); +#endif + /* Search through all items, but check the array first. */ + for (i = 0; i < owner->narr; i++) + { + if (owner->arr[i].item == value && + owner->arr[i].kind == kind) + { + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; -/* - * ResourceOwnerCreate - * Create an empty ResourceOwner. - * - * All ResourceOwner objects are kept in TopMemoryContext, since they should - * only be freed explicitly. - */ -ResourceOwner -ResourceOwnerCreate(ResourceOwner parent, const char *name) -{ - ResourceOwner owner; +#ifdef RESOWNER_STATS + narray_lookups++; +#endif - owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext, - sizeof(ResourceOwnerData)); - owner->name = name; + return; + } + } - if (parent) + /* Search hash */ + if (owner->nhash > 0) { - owner->parent = parent; - owner->nextchild = parent->firstchild; - parent->firstchild = owner; - } + uint32 mask = owner->capacity - 1; - ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer)); - ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->filearr), FileGetDatum(-1)); - ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL)); + idx = hash_resource_elem(value, kind) & mask; + for (i = 0; i < owner->capacity; i++) + { + if (owner->hash[idx].item == value && + owner->hash[idx].kind == kind) + { + owner->hash[idx].item = (Datum) 0; + owner->hash[idx].kind = NULL; + owner->nhash--; + +#ifdef RESOWNER_STATS + nhash_lookups++; +#endif + return; + } + idx = (idx + 1) & mask; + } + } - return owner; + /* + * Use %p to print the reference, since most objects tracked by a resource + * owner are pointers. It's a bit misleading if it's not a pointer, but + * this is a programmer error, anyway. + */ + elog(ERROR, "%s %p is not owned by resource owner %s", + kind->name, DatumGetPointer(value), owner->name); } /* @@ -486,6 +497,15 @@ ResourceOwnerRelease(ResourceOwner owner, { /* There's not currently any setup needed before recursing */ ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel); + +#ifdef RESOWNER_STATS + if (isTopLevel) + { + elog(LOG, "RESOWNER STATS: lookups: array %d, hash %d", narray_lookups, nhash_lookups); + narray_lookups = 0; + nhash_lookups = 0; + } +#endif } static void @@ -497,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceOwner child; ResourceOwner save; ResourceReleaseCallbackItem *item; - Datum foundres; /* Recurse to handle descendants */ for (child = owner->firstchild; child != NULL; child = child->nextchild) @@ -513,61 +532,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, if (phase == RESOURCE_RELEASE_BEFORE_LOCKS) { /* - * Release buffer pins. Note that ReleaseBuffer will remove the - * buffer entry from our array, so we just have to iterate till there - * are none. + * Release all references that need to be released before the locks. * - * During a commit, there shouldn't be any remaining pins --- that - * would indicate failure to clean up the executor correctly --- so - * issue warnings. In the abort case, just clean up quietly. + * During a commit, there shouldn't be any remaining references --- + * that would indicate failure to clean up the executor correctly --- + * so issue warnings. In the abort case, just clean up quietly. */ - while (ResourceArrayGetAny(&(owner->bufferarr), &foundres)) - { - Buffer res = DatumGetBuffer(foundres); - - if (isCommit) - PrintBufferLeakWarning(res); - ReleaseBuffer(res); - } - - /* Ditto for relcache references */ - while (ResourceArrayGetAny(&(owner->relrefarr), &foundres)) - { - Relation res = (Relation) DatumGetPointer(foundres); - - if (isCommit) - PrintRelCacheLeakWarning(res); - RelationClose(res); - } - - /* Ditto for dynamic shared memory segments */ - while (ResourceArrayGetAny(&(owner->dsmarr), &foundres)) - { - dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres); - - if (isCommit) - PrintDSMLeakWarning(res); - dsm_detach(res); - } - - /* Ditto for JIT contexts */ - while (ResourceArrayGetAny(&(owner->jitarr), &foundres)) - { - JitContext *context = (JitContext *) PointerGetDatum(foundres); - - jit_release_context(context); - } - - /* Ditto for cryptohash contexts */ - while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres)) - { - pg_cryptohash_ctx *context = - (pg_cryptohash_ctx *) PointerGetDatum(foundres); - - if (isCommit) - PrintCryptoHashLeakWarning(foundres); - pg_cryptohash_free(context); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } else if (phase == RESOURCE_RELEASE_LOCKS) { @@ -576,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, /* * For a top-level xact we are going to release all locks (or at * least all non-session locks), so just do a single lmgr call at - * the top of the recursion. + * the top of the recursion */ if (owner == TopTransactionResourceOwner) { @@ -620,70 +591,9 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) { /* - * Release catcache references. Note that ReleaseCatCache will remove - * the catref entry from our array, so we just have to iterate till - * there are none. - * - * As with buffer pins, warn if any are left at commit time. + * Release all references that need to be released after the locks. */ - while (ResourceArrayGetAny(&(owner->catrefarr), &foundres)) - { - HeapTuple res = (HeapTuple) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheLeakWarning(res); - ReleaseCatCache(res); - } - - /* Ditto for catcache lists */ - while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres)) - { - CatCList *res = (CatCList *) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheListLeakWarning(res); - ReleaseCatCacheList(res); - } - - /* Ditto for plancache references */ - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) - { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); - - if (isCommit) - PrintPlanCacheLeakWarning(res); - ReleaseCachedPlan(res, true); - } - - /* Ditto for tupdesc references */ - while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres)) - { - TupleDesc res = (TupleDesc) DatumGetPointer(foundres); - - if (isCommit) - PrintTupleDescLeakWarning(res); - DecrTupleDescRefCount(res); - } - - /* Ditto for snapshot references */ - while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres)) - { - Snapshot res = (Snapshot) DatumGetPointer(foundres); - - if (isCommit) - PrintSnapshotLeakWarning(res); - UnregisterSnapshot(res); - } - - /* Ditto for temporary files */ - while (ResourceArrayGetAny(&(owner->filearr), &foundres)) - { - File res = DatumGetFile(foundres); - - if (isCommit) - PrintFileLeakWarning(res); - FileClose(res); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } /* Let add-on modules get a chance too */ @@ -704,16 +614,48 @@ void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner) { ResourceOwner save; - Datum foundres; save = CurrentResourceOwner; CurrentResourceOwner = owner; - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + + /* array first */ + for (int i = 0; i < owner->narr; i++) + { + if (owner->arr[i].kind == &planref_resowner_funcs) + { + CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item); + + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; + i--; + + /* + * pass 'false' because we already removed the entry from the + * resowner + */ + ReleaseCachedPlan(planref, false); + } + } + + /* Then hash */ + for (int i = 0; i < owner->capacity; i++) { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + if (owner->hash[i].kind == &planref_resowner_funcs) + { + CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->hash[i].item); - ReleaseCachedPlan(res, true); + owner->hash[i].item = (Datum) 0; + owner->hash[i].kind = NULL; + owner->nhash--; + + /* + * pass 'false' because we already removed the entry from the + * resowner + */ + ReleaseCachedPlan(planref, false); + } } + CurrentResourceOwner = save; } @@ -730,19 +672,15 @@ ResourceOwnerDelete(ResourceOwner owner) Assert(owner != CurrentResourceOwner); /* And it better not own any resources, either */ - Assert(owner->bufferarr.nitems == 0); - Assert(owner->catrefarr.nitems == 0); - Assert(owner->catlistrefarr.nitems == 0); - Assert(owner->relrefarr.nitems == 0); - Assert(owner->planrefarr.nitems == 0); - Assert(owner->tupdescarr.nitems == 0); - Assert(owner->snapshotarr.nitems == 0); - Assert(owner->filearr.nitems == 0); - Assert(owner->dsmarr.nitems == 0); - Assert(owner->jitarr.nitems == 0); - Assert(owner->cryptohasharr.nitems == 0); + Assert(owner->narr == 0); + Assert(owner->nhash == 0); Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1); +#ifdef RESOWNER_TRACE + elog(LOG, "DELETE %d: %p %s", + resowner_trace_counter++, owner, owner->name); +#endif + /* * Delete children. The recursive call will delink the child from me, so * just iterate as long as there is a child. @@ -758,18 +696,8 @@ ResourceOwnerDelete(ResourceOwner owner) ResourceOwnerNewParent(owner, NULL); /* And free the object. */ - ResourceArrayFree(&(owner->bufferarr)); - ResourceArrayFree(&(owner->catrefarr)); - ResourceArrayFree(&(owner->catlistrefarr)); - ResourceArrayFree(&(owner->relrefarr)); - ResourceArrayFree(&(owner->planrefarr)); - ResourceArrayFree(&(owner->tupdescarr)); - ResourceArrayFree(&(owner->snapshotarr)); - ResourceArrayFree(&(owner->filearr)); - ResourceArrayFree(&(owner->dsmarr)); - ResourceArrayFree(&(owner->jitarr)); - ResourceArrayFree(&(owner->cryptohasharr)); - + if (owner->hash) + pfree(owner->hash); pfree(owner); } @@ -827,11 +755,10 @@ ResourceOwnerNewParent(ResourceOwner owner, /* * Register or deregister callback functions for resource cleanup * - * These functions are intended for use by dynamically loaded modules. - * For built-in modules we generally just hardwire the appropriate calls. - * - * Note that the callback occurs post-commit or post-abort, so the callback - * functions can only do noncritical cleanup. + * These functions can be used by dynamically loaded modules. These used + * to be the only way for an extension to register custom resource types + * with a resource owner, but nowadays it is easier to define a new + * ResourceOwnerFuncs instance with custom callbacks. */ void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg) @@ -922,44 +849,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg) ReleaseAuxProcessResources(isCommit); } - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * buffer array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeBuffers(ResourceOwner owner) -{ - /* We used to allow pinning buffers without a resowner, but no more */ - Assert(owner != NULL); - ResourceArrayEnlarge(&(owner->bufferarr)); -} - -/* - * Remember that a buffer pin is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeBuffers() - */ -void -ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer) -{ - ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer)); -} - -/* - * Forget that a buffer pin is owned by a ResourceOwner - */ -void -ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) -{ - if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer))) - elog(ERROR, "buffer %d is not owned by resource owner %s", - buffer, owner->name); -} - /* * Remember that a Local Lock is owned by a ResourceOwner * @@ -1011,424 +900,3 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock) elog(ERROR, "lock reference %p is not owned by resource owner %s", locallock, owner->name); } - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * catcache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->catrefarr)); -} - -/* - * Remember that a catcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs() - */ -void -ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple)); -} - -/* - * Forget that a catcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple))) - elog(ERROR, "catcache reference %p is not owned by resource owner %s", - tuple, owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * catcache-list reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->catlistrefarr)); -} - -/* - * Remember that a catcache-list reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs() - */ -void -ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list)); -} - -/* - * Forget that a catcache-list reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list))) - elog(ERROR, "catcache list reference %p is not owned by resource owner %s", - list, owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * relcache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeRelationRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->relrefarr)); -} - -/* - * Remember that a relcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeRelationRefs() - */ -void -ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel) -{ - ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel)); -} - -/* - * Forget that a relcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel) -{ - if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel))) - elog(ERROR, "relcache reference %s is not owned by resource owner %s", - RelationGetRelationName(rel), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintRelCacheLeakWarning(Relation rel) -{ - elog(WARNING, "relcache reference leak: relation \"%s\" not closed", - RelationGetRelationName(rel)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * plancache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->planrefarr)); -} - -/* - * Remember that a plancache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs() - */ -void -ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan)); -} - -/* - * Forget that a plancache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan))) - elog(ERROR, "plancache reference %p is not owned by resource owner %s", - plan, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintPlanCacheLeakWarning(CachedPlan *plan) -{ - elog(WARNING, "plancache reference leak: plan %p not closed", plan); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * tupdesc reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeTupleDescs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->tupdescarr)); -} - -/* - * Remember that a tupdesc reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeTupleDescs() - */ -void -ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc)); -} - -/* - * Forget that a tupdesc reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc))) - elog(ERROR, "tupdesc reference %p is not owned by resource owner %s", - tupdesc, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintTupleDescLeakWarning(TupleDesc tupdesc) -{ - elog(WARNING, - "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced", - tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * snapshot reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeSnapshots(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->snapshotarr)); -} - -/* - * Remember that a snapshot reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeSnapshots() - */ -void -ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot)); -} - -/* - * Forget that a snapshot reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot))) - elog(ERROR, "snapshot reference %p is not owned by resource owner %s", - snapshot, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintSnapshotLeakWarning(Snapshot snapshot) -{ - elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", - snapshot); -} - - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * files reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeFiles(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->filearr)); -} - -/* - * Remember that a temporary file is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeFiles() - */ -void -ResourceOwnerRememberFile(ResourceOwner owner, File file) -{ - ResourceArrayAdd(&(owner->filearr), FileGetDatum(file)); -} - -/* - * Forget that a temporary file is owned by a ResourceOwner - */ -void -ResourceOwnerForgetFile(ResourceOwner owner, File file) -{ - if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file))) - elog(ERROR, "temporary file %d is not owned by resource owner %s", - file, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintFileLeakWarning(File file) -{ - elog(WARNING, "temporary file leak: File %d still referenced", - file); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * dynamic shmem segment reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeDSMs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->dsmarr)); -} - -/* - * Remember that a dynamic shmem segment is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeDSMs() - */ -void -ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg) -{ - ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg)); -} - -/* - * Forget that a dynamic shmem segment is owned by a ResourceOwner - */ -void -ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg) -{ - if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg))) - elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s", - dsm_segment_handle(seg), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintDSMLeakWarning(dsm_segment *seg) -{ - elog(WARNING, "dynamic shared memory leak: segment %u still referenced", - dsm_segment_handle(seg)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * JIT context reference array. - * - * This is separate from actually inserting an entry because if we run out of - * memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeJIT(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->jitarr)); -} - -/* - * Remember that a JIT context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeJIT() - */ -void -ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->jitarr), handle); -} - -/* - * Forget that a JIT context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->jitarr), handle)) - elog(ERROR, "JIT context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * cryptohash context reference array. - * - * This is separate from actually inserting an entry because if we run out of - * memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCryptoHash(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->cryptohasharr)); -} - -/* - * Remember that a cryptohash context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCryptoHash() - */ -void -ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->cryptohasharr), handle); -} - -/* - * Forget that a cryptohash context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->cryptohasharr), handle)) - elog(ERROR, "cryptohash context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintCryptoHashLeakWarning(Datum handle) -{ - elog(WARNING, "cryptohash context reference leak: context %p still referenced", - DatumGetPointer(handle)); -} diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index ae16c3ed7d6..3ded16f6fc8 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -66,7 +66,7 @@ #include "utils/memutils.h" #include "utils/old_snapshot.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/timestamp.h" @@ -174,6 +174,24 @@ static Snapshot CopySnapshot(Snapshot snapshot); static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); +/* ResourceOwner callbacks to track snapshot references */ +static void ResOwnerReleaseSnapshot(Datum res); +static void ResOwnerPrintSnapshotLeakWarning(Datum res); + +static ResourceOwnerFuncs snapshot_resowner_funcs = +{ + .name = "snapshot reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseSnapshot, + .PrintLeakWarning = ResOwnerPrintSnapshotLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberSnapshot(owner, snap) \ + ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_funcs) +#define ResourceOwnerForgetSnapshot(owner, snap) \ + ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_funcs) + /* * Snapshot fields to be serialized. * @@ -831,7 +849,7 @@ RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner) snap = snapshot->copied ? snapshot : CopySnapshot(snapshot); /* and tell resowner.c about it */ - ResourceOwnerEnlargeSnapshots(owner); + ResourceOwnerEnlarge(owner); snap->regd_count++; ResourceOwnerRememberSnapshot(owner, snap); @@ -2345,3 +2363,19 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) return false; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseSnapshot(Datum res) +{ + UnregisterSnapshot((Snapshot) DatumGetPointer(res)); +} + +static void +ResOwnerPrintSnapshotLeakWarning(Datum res) +{ + elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", + DatumGetPointer(res)); +} diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c index 551ec392b60..6421f1de5cd 100644 --- a/src/common/cryptohash_openssl.c +++ b/src/common/cryptohash_openssl.c @@ -27,7 +27,6 @@ #ifndef FRONTEND #include "utils/memutils.h" #include "utils/resowner.h" -#include "utils/resowner_private.h" #endif /* @@ -60,6 +59,27 @@ struct pg_cryptohash_ctx #endif }; +/* ResourceOwner callbacks to hold cryptohash contexts */ +#ifndef FRONTEND +static void ResOwnerReleaseCryptoHash(Datum res); +static void ResOwnerPrintCryptoHashLeakWarning(Datum res); + +static ResourceOwnerFuncs cryptohash_resowner_funcs = +{ + /* relcache references */ + .name = "OpenSSL cryptohash context", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseCryptoHash, + .PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning, +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberCryptoHash(owner, ctx) \ + ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs) +#define ResourceOwnerForgetCryptoHash(owner, ctx) \ + ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs) +#endif + /* * pg_cryptohash_create * @@ -77,7 +97,7 @@ pg_cryptohash_create(pg_cryptohash_type type) * allocation to avoid leaking. */ #ifndef FRONTEND - ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); #endif ctx = ALLOC(sizeof(pg_cryptohash_ctx)); @@ -106,8 +126,7 @@ pg_cryptohash_create(pg_cryptohash_type type) #ifndef FRONTEND ctx->resowner = CurrentResourceOwner; - ResourceOwnerRememberCryptoHash(CurrentResourceOwner, - PointerGetDatum(ctx)); + ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx); #endif return ctx; @@ -207,10 +226,28 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx) EVP_MD_CTX_destroy(ctx->evpctx); #ifndef FRONTEND - ResourceOwnerForgetCryptoHash(ctx->resowner, - PointerGetDatum(ctx)); + ResourceOwnerForgetCryptoHash(ctx->resowner, ctx); #endif explicit_bzero(ctx, sizeof(pg_cryptohash_ctx)); FREE(ctx); } + + +/* + * ResourceOwner callbacks + */ +#ifndef FRONTEND +static void +ResOwnerReleaseCryptoHash(Datum res) +{ + pg_cryptohash_free((pg_cryptohash_ctx *) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCryptoHashLeakWarning(Datum res) +{ + elog(WARNING, "cryptohash context reference leak: context %p still referenced", + DatumGetPointer(res)); +} +#endif diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index f6b57829653..6815dc38a16 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -296,6 +296,15 @@ typedef struct CkptSortItem extern CkptSortItem *CkptBufferIds; +/* ResourceOwner callbacks to hold buffer pins */ +extern ResourceOwnerFuncs buffer_resowner_funcs; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberBuffer(owner, buffer) \ + ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs) +#define ResourceOwnerForgetBuffer(owner, buffer) \ + ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_resowner_funcs) + /* * Internal buffer management routines */ diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index ddc2762eb3f..2bf95c22e84 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -225,7 +225,4 @@ extern void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple newtuple, void (*function) (int, uint32, Oid)); -extern void PrintCatCacheLeakWarning(HeapTuple tuple); -extern void PrintCatCacheListLeakWarning(CatCList *list); - #endif /* CATCACHE_H */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 79d96e5ed03..964af79b30d 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -233,4 +233,6 @@ extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource, extern CachedExpression *GetCachedExpression(Node *expr); extern void FreeCachedExpression(CachedExpression *cexpr); +extern ResourceOwnerFuncs planref_resowner_funcs; + #endif /* PLANCACHE_H */ diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index 109ac31b248..d59d14c0d01 100644 --- a/src/include/utils/resowner.h +++ b/src/include/utils/resowner.h @@ -50,6 +50,36 @@ typedef enum RESOURCE_RELEASE_AFTER_LOCKS } ResourceReleasePhase; +/* + * In order to track an object, resowner.c needs a few callbacks for it. + * The callbacks for an resource of a specific kind are encapsulated in + * ResourceOwnerFuncs. + * + * Note that the callback occurs post-commit or post-abort, so these callback + * functions can only do noncritical cleanup. + */ +typedef struct ResourceOwnerFuncs +{ + const char *name; /* name for the object kind, for debugging */ + + ResourceReleasePhase phase; /* when are these objects released? */ + + /* + * Release resource. + * + * NOTE: this must call ResourceOwnerForget to disassociate it with the + * resource owner. + */ + void (*ReleaseResource)(Datum res); + + /* + * Print a warning, when a resource has not been properly released before + * commit. + */ + void (*PrintLeakWarning)(Datum res); + +} ResourceOwnerFuncs; + /* * Dynamically loaded modules can get control during ResourceOwnerRelease * by providing a callback of this form. @@ -71,16 +101,29 @@ extern void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); -extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner); extern void ResourceOwnerDelete(ResourceOwner owner); extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); extern void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent); + +extern void ResourceOwnerEnlarge(ResourceOwner owner); +extern void ResourceOwnerRemember(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind); +extern void ResourceOwnerForget(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind); + extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg); extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg); + extern void CreateAuxProcessResourceOwner(void); extern void ReleaseAuxProcessResources(bool isCommit); +/* special support for local lock management */ +struct LOCALLOCK; +extern void ResourceOwnerRememberLock(ResourceOwner owner, struct LOCALLOCK *locallock); +extern void ResourceOwnerForgetLock(ResourceOwner owner, struct LOCALLOCK *locallock); + +/* special function to relase all plancache references */ +extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner); + #endif /* RESOWNER_H */ diff --git a/src/include/utils/resowner_private.h b/src/include/utils/resowner_private.h deleted file mode 100644 index c480a1a24be..00000000000 --- a/src/include/utils/resowner_private.h +++ /dev/null @@ -1,105 +0,0 @@ -/*------------------------------------------------------------------------- - * - * resowner_private.h - * POSTGRES resource owner private definitions. - * - * See utils/resowner/README for more info. - * - * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/utils/resowner_private.h - * - *------------------------------------------------------------------------- - */ -#ifndef RESOWNER_PRIVATE_H -#define RESOWNER_PRIVATE_H - -#include "storage/dsm.h" -#include "storage/fd.h" -#include "storage/lock.h" -#include "utils/catcache.h" -#include "utils/plancache.h" -#include "utils/resowner.h" -#include "utils/snapshot.h" - - -/* support for buffer refcount management */ -extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner); -extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer); -extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer); - -/* support for local lock management */ -extern void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock); -extern void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock); - -/* support for catcache refcount management */ -extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner); -extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner, - HeapTuple tuple); -extern void ResourceOwnerForgetCatCacheRef(ResourceOwner owner, - HeapTuple tuple); -extern void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner); -extern void ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, - CatCList *list); -extern void ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, - CatCList *list); - -/* support for relcache refcount management */ -extern void ResourceOwnerEnlargeRelationRefs(ResourceOwner owner); -extern void ResourceOwnerRememberRelationRef(ResourceOwner owner, - Relation rel); -extern void ResourceOwnerForgetRelationRef(ResourceOwner owner, - Relation rel); - -/* support for plancache refcount management */ -extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner); -extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, - CachedPlan *plan); -extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, - CachedPlan *plan); - -/* support for tupledesc refcount management */ -extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner); -extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner, - TupleDesc tupdesc); -extern void ResourceOwnerForgetTupleDesc(ResourceOwner owner, - TupleDesc tupdesc); - -/* support for snapshot refcount management */ -extern void ResourceOwnerEnlargeSnapshots(ResourceOwner owner); -extern void ResourceOwnerRememberSnapshot(ResourceOwner owner, - Snapshot snapshot); -extern void ResourceOwnerForgetSnapshot(ResourceOwner owner, - Snapshot snapshot); - -/* support for temporary file management */ -extern void ResourceOwnerEnlargeFiles(ResourceOwner owner); -extern void ResourceOwnerRememberFile(ResourceOwner owner, - File file); -extern void ResourceOwnerForgetFile(ResourceOwner owner, - File file); - -/* support for dynamic shared memory management */ -extern void ResourceOwnerEnlargeDSMs(ResourceOwner owner); -extern void ResourceOwnerRememberDSM(ResourceOwner owner, - dsm_segment *); -extern void ResourceOwnerForgetDSM(ResourceOwner owner, - dsm_segment *); - -/* support for JITContext management */ -extern void ResourceOwnerEnlargeJIT(ResourceOwner owner); -extern void ResourceOwnerRememberJIT(ResourceOwner owner, - Datum handle); -extern void ResourceOwnerForgetJIT(ResourceOwner owner, - Datum handle); - -/* support for cryptohash context management */ -extern void ResourceOwnerEnlargeCryptoHash(ResourceOwner owner); -extern void ResourceOwnerRememberCryptoHash(ResourceOwner owner, - Datum handle); -extern void ResourceOwnerForgetCryptoHash(ResourceOwner owner, - Datum handle); - -#endif /* RESOWNER_PRIVATE_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 943142ced8c..2df8d3cbefa 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2127,8 +2127,10 @@ ReplicationStateOnDisk ResTarget ReservoirState ReservoirStateData -ResourceArray +ResourceElem ResourceOwner +ResourceOwnerData +ResourceOwnerFuncs ResourceReleaseCallback ResourceReleaseCallbackItem ResourceReleasePhase -- 2.29.2 --------------F30A5443A5D524A5CBC51AE7 Content-Type: text/x-patch; charset=UTF-8; name="v6-0003-Optimize-hash-function.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="v6-0003-Optimize-hash-function.patch" ^ permalink raw reply [nested|flat] 2+ messages in thread
* New Object Access Type hooks @ 2022-03-18 03:21 Mark Dilger <[email protected]> 0 siblings, 0 replies; 2+ messages in thread From: Mark Dilger @ 2022-03-18 03:21 UTC (permalink / raw) To: pgsql-hackers; Joshua Brindle <[email protected]>; +Cc: Robert Haas <[email protected]>; Tom Lane <[email protected]>; Andrew Dunstan <[email protected]>; Jeff Davis <[email protected]>; Joe Conway <[email protected]> Hackers, Over in [1], Joshua proposed a new set of Object Access Type hooks based on strings rather than Oids. His patch was written to be applied atop my patch for granting privileges on gucs. On review of his patch, I became uncomfortable with the complete lack of regression test coverage. To be fair, he did paste a bit of testing logic to the thread, but it appears to be based on pgaudit, and it is unclear how to include such a test in the core project, where pgaudit is not assumed to be installed. First, I refactored his patch to work against HEAD and not depend on my GUCs patch. Find that as v1-0001. The refactoring exposed a bit of a problem. To call the new hook for SET and ALTER SYSTEM commands, I need to pass in the Oid of a catalog table. But since my GUC patch isn't applied yet, there isn't any such table (pg_setting_acl or whatnot) to pass. So I'm passing InvalidOid, but I don't know if that is right. In any event, if we want a new API like this, we should think a bit harder about whether it can be used to check operations where no table Oid is applicable. Second, I added a new test directory, src/test/modules/test_oat_hooks, which includes a new loadable module with hook implementations and a regression test for testing the object access hooks. The main point of the test is to log which hooks get called in which order, and which hooks do or do not get called when other hooks allow or deny access. That information shows up in the expected output as NOTICE messages. This second patch has gotten a little long, and I'd like another pair of eyes on this before spending a second day on the effort. Please note that this is a quick WIP patch in response to the patch Joshua posted earlier today. Sorry for sometimes missing function comments, etc. The goal, if this design seems acceptable, is to polish this, hopefully with Joshua's assistance, and get it committed *before* my GUCs patch, so that my patch can be rebased to use it. Otherwise, if this is rejected, I can continue on the GUC patch without this. (FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.) [1] https://www.postgresql.org/message-id/flat/664799.1647456444%40sss.pgh.pa.us#c9721c2da88d59684ac7ac5... — Mark Dilger EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company Attachments: [application/octet-stream] v1-0001-Add-String-object-access-hooks.patch (10.8K, ../../[email protected]/2-v1-0001-Add-String-object-access-hooks.patch) download | inline diff: From f994037f851a8cff27c492433dd37030f6c5e15d Mon Sep 17 00:00:00 2001 From: Mark Dilger <[email protected]> Date: Thu, 17 Mar 2022 19:30:09 -0700 Subject: [PATCH v1 1/2] Add String object access hooks The first user of these will be the GUC access controls Written by Joshua Brindle; refactored by Mark Dilger --- src/backend/catalog/objectaccess.c | 128 +++++++++++++++++++++++++++++ src/backend/utils/misc/guc.c | 17 ++++ src/include/catalog/objectaccess.h | 70 +++++++++++++++- src/include/nodes/parsenodes.h | 4 +- src/include/utils/acl.h | 4 +- 5 files changed, 220 insertions(+), 3 deletions(-) diff --git a/src/backend/catalog/objectaccess.c b/src/backend/catalog/objectaccess.c index 549fac4539..72ad6e9a90 100644 --- a/src/backend/catalog/objectaccess.c +++ b/src/backend/catalog/objectaccess.c @@ -20,6 +20,8 @@ * and logging plugins. */ object_access_hook_type object_access_hook = NULL; +object_access_hook_type_str object_access_hook_str = NULL; + /* * RunObjectPostCreateHook @@ -143,3 +145,129 @@ RunFunctionExecuteHook(Oid objectId) ProcedureRelationId, objectId, 0, NULL); } + +/* String versions */ + + +/* + * RunObjectPostCreateHook + * + * It is entrypoint of OAT_POST_CREATE event + */ +void +RunObjectPostCreateHookStr(Oid classId, const char *objectName, int subId, + bool is_internal) +{ + ObjectAccessPostCreate pc_arg; + + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + memset(&pc_arg, 0, sizeof(ObjectAccessPostCreate)); + pc_arg.is_internal = is_internal; + + (*object_access_hook_str) (OAT_POST_CREATE, + classId, objectName, subId, + (void *) &pc_arg); +} + +/* + * RunObjectDropHook + * + * It is entrypoint of OAT_DROP event + */ +void +RunObjectDropHookStr(Oid classId, const char *objectName, int subId, + int dropflags) +{ + ObjectAccessDrop drop_arg; + + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + memset(&drop_arg, 0, sizeof(ObjectAccessDrop)); + drop_arg.dropflags = dropflags; + + (*object_access_hook_str) (OAT_DROP, + classId, objectName, subId, + (void *) &drop_arg); +} + +/* + * RunObjectTruncateHook + * + * It is the entrypoint of OAT_TRUNCATE event + */ +void +RunObjectTruncateHookStr(const char *objectName) +{ + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + (*object_access_hook_str) (OAT_TRUNCATE, + RelationRelationId, objectName, 0, + NULL); +} + +/* + * RunObjectPostAlterHook + * + * It is entrypoint of OAT_POST_ALTER event + */ +void +RunObjectPostAlterHookStr(Oid classId, const char *objectName, int subId, + Oid auxiliaryId, bool is_internal) +{ + ObjectAccessPostAlter pa_arg; + + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + memset(&pa_arg, 0, sizeof(ObjectAccessPostAlter)); + pa_arg.auxiliary_id = auxiliaryId; + pa_arg.is_internal = is_internal; + + (*object_access_hook_str) (OAT_POST_ALTER, + classId, objectName, subId, + (void *) &pa_arg); +} + +/* + * RunNamespaceSearchHook + * + * It is entrypoint of OAT_NAMESPACE_SEARCH event + */ +bool +RunNamespaceSearchHookStr(const char *objectName, bool ereport_on_violation) +{ + ObjectAccessNamespaceSearch ns_arg; + + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + memset(&ns_arg, 0, sizeof(ObjectAccessNamespaceSearch)); + ns_arg.ereport_on_violation = ereport_on_violation; + ns_arg.result = true; + + (*object_access_hook_str) (OAT_NAMESPACE_SEARCH, + NamespaceRelationId, objectName, 0, + (void *) &ns_arg); + + return ns_arg.result; +} + +/* + * RunFunctionExecuteHook + * + * It is entrypoint of OAT_FUNCTION_EXECUTE event + */ +void +RunFunctionExecuteHookStr(const char *objectName) +{ + /* caller should check, but just in case... */ + Assert(object_access_hook_str != NULL); + + (*object_access_hook_str) (OAT_FUNCTION_EXECUTE, + ProcedureRelationId, objectName, 0, + NULL); +} diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index e7f0a380e6..932aefc777 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -43,6 +43,7 @@ #include "access/xlog_internal.h" #include "access/xlogrecovery.h" #include "catalog/namespace.h" +#include "catalog/objectaccess.h" #include "catalog/pg_authid.h" #include "catalog/storage.h" #include "commands/async.h" @@ -8736,6 +8737,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) replace_auto_config_value(&head, &tail, name, value); } + /* + * Invoke the post-alter hook for altering this GUC variable. + * + * We do this here rather than at the end, because ALTER SYSTEM is not + * transactional. If the hook aborts our transaction, it will be cleaner + * to do so before we touch any files. + */ + InvokeObjectPostAlterHookArgStr(InvalidOid, name, + ACL_ALTER_SYSTEM, + altersysstmt->setstmt->kind, + false); + /* * To ensure crash safety, first write the new file data to a temp file, * then atomically rename it into place. @@ -8907,6 +8920,10 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) ResetAllOptions(); break; } + + /* Invoke the post-alter hook for setting this GUC variable. */ + InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name, + ACL_SET_VALUE, stmt->kind, false); } /* diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h index 508dfd0a6b..4d54ae2a7d 100644 --- a/src/include/catalog/objectaccess.h +++ b/src/include/catalog/objectaccess.h @@ -121,15 +121,23 @@ typedef struct bool result; } ObjectAccessNamespaceSearch; -/* Plugin provides a hook function matching this signature. */ +/* Plugin provides a hook function matching one or both of these signatures. */ typedef void (*object_access_hook_type) (ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg); +typedef void (*object_access_hook_type_str) (ObjectAccessType access, + Oid classId, + const char *objectStr, + int subId, + void *arg); + /* Plugin sets this variable to a suitable hook function. */ extern PGDLLIMPORT object_access_hook_type object_access_hook; +extern PGDLLIMPORT object_access_hook_type_str object_access_hook_str; + /* Core code uses these functions to call the hook (see macros below). */ extern void RunObjectPostCreateHook(Oid classId, Oid objectId, int subId, @@ -142,6 +150,18 @@ extern void RunObjectPostAlterHook(Oid classId, Oid objectId, int subId, extern bool RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation); extern void RunFunctionExecuteHook(Oid objectId); +/* String versions */ +extern void RunObjectPostCreateHookStr(Oid classId, const char *objectStr, int subId, + bool is_internal); +extern void RunObjectDropHookStr(Oid classId, const char *objectStr, int subId, + int dropflags); +extern void RunObjectTruncateHookStr(const char *objectStr); +extern void RunObjectPostAlterHookStr(Oid classId, const char *objectStr, int subId, + Oid auxiliaryId, bool is_internal); +extern bool RunNamespaceSearchHookStr(const char *objectStr, bool ereport_on_violation); +extern void RunFunctionExecuteHookStr(const char *objectStr); + + /* * The following macros are wrappers around the functions above; these should * normally be used to invoke the hook in lieu of calling the above functions @@ -194,4 +214,52 @@ extern void RunFunctionExecuteHook(Oid objectId); RunFunctionExecuteHook(objectId); \ } while(0) + +#define InvokeObjectPostCreateHookStr(classId,objectName,subId) \ + InvokeObjectPostCreateHookArgStr((classId),(objectName),(subId),false) +#define InvokeObjectPostCreateHookArgStr(classId,objectName,subId,is_internal) \ + do { \ + if (object_access_hook_str) \ + RunObjectPostCreateHookStr((classId),(objectName),(subId), \ + (is_internal)); \ + } while(0) + +#define InvokeObjectDropHookStr(classId,objectName,subId) \ + InvokeObjectDropHookArgStr((classId),(objectName),(subId),0) +#define InvokeObjectDropHookArgStr(classId,objectName,subId,dropflags) \ + do { \ + if (object_access_hook_str) \ + RunObjectDropHookStr((classId),(objectName),(subId), \ + (dropflags)); \ + } while(0) + +#define InvokeObjectTruncateHookStr(objectName) \ + do { \ + if (object_access_hook_str) \ + RunObjectTruncateHookStr(objectName); \ + } while(0) + +#define InvokeObjectPostAlterHookStr(className,objectName,subId) \ + InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId), \ + InvalidOid,false) +#define InvokeObjectPostAlterHookArgStr(classId,objectName,subId, \ + auxiliaryId,is_internal) \ + do { \ + if (object_access_hook_str) \ + RunObjectPostAlterHookStr((classId),(objectName),(subId), \ + (auxiliaryId),(is_internal)); \ + } while(0) + +#define InvokeNamespaceSearchHookStr(objectName, ereport_on_violation) \ + (!object_access_hook_str \ + ? true \ + : RunNamespaceSearchHookStr((objectName), (ereport_on_violation))) + +#define InvokeFunctionExecuteHookStr(objectName) \ + do { \ + if (object_access_hook_str) \ + RunFunctionExecuteHookStr(objectName); \ + } while(0) + + #endif /* OBJECTACCESS_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1617702d9d..dc361d62e2 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -92,7 +92,9 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_CREATE (1<<9) /* for namespaces and databases */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ -#define N_ACL_RIGHTS 12 /* 1 plus the last 1<<x */ +#define ACL_SET_VALUE (1<<12) /* for configuration parameters */ +#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */ +#define N_ACL_RIGHTS 14 /* 1 plus the last 1<<x */ #define ACL_NO_RIGHTS 0 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */ #define ACL_SELECT_FOR_UPDATE ACL_UPDATE diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 1ce4c5556e..91ce3d8e9c 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -146,9 +146,11 @@ typedef struct ArrayType Acl; #define ACL_CREATE_CHR 'C' #define ACL_CREATE_TEMP_CHR 'T' #define ACL_CONNECT_CHR 'c' +#define ACL_SET_VALUE_CHR 's' +#define ACL_ALTER_SYSTEM_CHR 'A' /* string holding all privilege code chars, in order by bitmask position */ -#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTc" +#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsA" /* * Bitmasks defining "all rights" for each supported object type -- 2.35.1 [application/octet-stream] v1-0002-Add-regression-tests-of-Object-Access-Type-hooks.patch (56.5K, ../../[email protected]/3-v1-0002-Add-regression-tests-of-Object-Access-Type-hooks.patch) download | inline diff: From 77b7d000b750e6103c8af2769e9187e59d854af8 Mon Sep 17 00:00:00 2001 From: Mark Dilger <[email protected]> Date: Thu, 17 Mar 2022 15:05:18 -0700 Subject: [PATCH v1 2/2] Add regression tests of Object Access Type hooks --- src/test/modules/Makefile | 1 + src/test/modules/test_oat_hooks/.gitignore | 4 + src/test/modules/test_oat_hooks/Makefile | 27 + src/test/modules/test_oat_hooks/README | 16 + .../expected/test_oat_hooks.out | 213 ++++ .../modules/test_oat_hooks/oat_hooks.conf | 1 + .../test_oat_hooks/sql/test_oat_hooks.sql | 59 ++ .../modules/test_oat_hooks/test_oat_hooks.c | 935 ++++++++++++++++++ .../test_oat_hooks/test_oat_hooks.control | 4 + .../modules/test_oat_hooks/test_oat_hooks.h | 25 + 10 files changed, 1285 insertions(+) create mode 100644 src/test/modules/test_oat_hooks/.gitignore create mode 100644 src/test/modules/test_oat_hooks/Makefile create mode 100644 src/test/modules/test_oat_hooks/README create mode 100644 src/test/modules/test_oat_hooks/expected/test_oat_hooks.out create mode 100644 src/test/modules/test_oat_hooks/oat_hooks.conf create mode 100644 src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.c create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.control create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.h diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index dffc79b2d9..9090226daa 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -20,6 +20,7 @@ SUBDIRS = \ test_ginpostinglist \ test_integerset \ test_misc \ + test_oat_hooks \ test_parser \ test_pg_dump \ test_predtest \ diff --git a/src/test/modules/test_oat_hooks/.gitignore b/src/test/modules/test_oat_hooks/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_oat_hooks/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_oat_hooks/Makefile b/src/test/modules/test_oat_hooks/Makefile new file mode 100644 index 0000000000..ff249a6d91 --- /dev/null +++ b/src/test/modules/test_oat_hooks/Makefile @@ -0,0 +1,27 @@ +# src/test/modules/test_oat_hooks/Makefile + +MODULE_big = test_oat_hooks +OBJS = \ + $(WIN32RES) \ + test_oat_hooks.o +PGFILEDESC = "test_oat_hooks - example use of object access hooks" + +EXTENSION = test_oat_hooks +# DATA = test_oat_hooks--1.0.sql + +REGRESS = test_oat_hooks +REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_oat_hooks/oat_hooks.conf +# Disabled because these tests require "shared_preload_libraries=test_oat_hooks", +# which typical installcheck users do not have (e.g. buildfarm clients). +NO_INSTALLCHECK = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_oat_hooks +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_oat_hooks/README b/src/test/modules/test_oat_hooks/README new file mode 100644 index 0000000000..015deb25d4 --- /dev/null +++ b/src/test/modules/test_oat_hooks/README @@ -0,0 +1,16 @@ +test_oat_hooks is an example of how to use the object access hooks to +enforce mandatory access controls. + +Functions +========= +test_rls_hooks_permissive(CmdType cmdtype, Relation relation) + RETURNS List* + +Returns a list of policies which should be added to any existing +policies on the relation, combined with OR. + +test_rls_hooks_restrictive(CmdType cmdtype, Relation relation) + RETURNS List* + +Returns a list of policies which should be added to any existing +policies on the relation, combined with AND. diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out new file mode 100644 index 0000000000..b0e3fe6e68 --- /dev/null +++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out @@ -0,0 +1,213 @@ +LOAD 'test_oat_hooks'; +-- Turn on logging messages to see audit messages +SET client_min_messages = 'LOG'; +-- SET commands fire both the ProcessUtility_hook and the +-- object_access_hook_str. Since the auditing GUC starts out false, we miss the +-- initial "attempting" audit message from the ProcessUtility_hook, but we +-- should thereafter see the audit messages +SET test_oat_hooks.audit = true; +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit] +NOTICE: in process utility: superuser finished set +-- Create objects for use in the test +CREATE USER regress_test_user; +NOTICE: in process utility: superuser attempting CreateRoleStmt +NOTICE: in object access: superuser attempting create (subId=0) [explicit] +NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in process utility: superuser finished CreateRoleStmt +CREATE TABLE regress_test_table (t text); +NOTICE: in process utility: superuser attempting CreateStmt +NOTICE: in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed] +LINE 1: CREATE TABLE regress_test_table (t text); + ^ +NOTICE: in object access: superuser finished namespace search (subId=0) [no report on violation, allowed] +LINE 1: CREATE TABLE regress_test_table (t text); + ^ +NOTICE: in object access: superuser attempting create (subId=0) [explicit] +NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0) [explicit] +NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0) [explicit] +NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0) [internal] +NOTICE: in object access: superuser finished create (subId=0) [internal] +NOTICE: in object access: superuser attempting create (subId=0) [internal] +NOTICE: in object access: superuser finished create (subId=0) [internal] +NOTICE: in process utility: superuser finished CreateStmt +GRANT SELECT ON Table regress_test_table TO public; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$ + SELECT $1; +$$ LANGUAGE sql; +NOTICE: in process utility: superuser attempting CreateFunctionStmt +NOTICE: in object access: superuser attempting create (subId=0) [explicit] +NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in process utility: superuser finished CreateFunctionStmt +GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +-- Do a few things as superuser +SELECT * FROM regress_test_table; +NOTICE: in executor check perms: superuser attempting execute +NOTICE: in executor check perms: superuser finished execute + t +--- +(0 rows) + +SELECT regress_test_func('arg'); +NOTICE: in executor check perms: superuser attempting execute +NOTICE: in executor check perms: superuser finished execute + regress_test_func +------------------- + arg +(1 row) + +SET work_mem = 8192; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in process utility: superuser finished set +RESET work_mem; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in process utility: superuser finished set +ALTER SYSTEM SET work_mem = 8192; +NOTICE: in process utility: superuser attempting alter system +NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in process utility: superuser finished alter system +ALTER SYSTEM RESET work_mem; +NOTICE: in process utility: superuser attempting alter system +NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in process utility: superuser finished alter system +-- Do those same things as non-superuser +SET SESSION AUTHORIZATION regress_test_user; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [session_authorization] +NOTICE: in object_access_hook_str: non-superuser finished alter (set) [session_authorization] +NOTICE: in process utility: non-superuser finished set +SELECT * FROM regress_test_table; +NOTICE: in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed] +LINE 1: SELECT * FROM regress_test_table; + ^ +NOTICE: in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed] +LINE 1: SELECT * FROM regress_test_table; + ^ +NOTICE: in executor check perms: non-superuser attempting execute +NOTICE: in executor check perms: non-superuser finished execute + t +--- +(0 rows) + +SELECT regress_test_func('arg'); +NOTICE: in executor check perms: non-superuser attempting execute +NOTICE: in executor check perms: non-superuser finished execute + regress_test_func +------------------- + arg +(1 row) + +SET work_mem = 8192; +NOTICE: in process utility: non-superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser finished alter (set) [work_mem] +NOTICE: in process utility: non-superuser finished set +RESET work_mem; +NOTICE: in process utility: non-superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser finished alter (set) [work_mem] +NOTICE: in process utility: non-superuser finished set +ALTER SYSTEM SET work_mem = 8192; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: must be superuser to execute ALTER SYSTEM command +ALTER SYSTEM RESET work_mem; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: must be superuser to execute ALTER SYSTEM command +RESET SESSION AUTHORIZATION; +NOTICE: in process utility: non-superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [session_authorization] +NOTICE: in object_access_hook_str: superuser finished alter (set) [session_authorization] +NOTICE: in process utility: superuser finished set +-- Turn off non-superuser permissions +SET test_oat_hooks.deny_set_variable = true; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable] +NOTICE: in process utility: superuser finished set +SET test_oat_hooks.deny_alter_system = true; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system] +NOTICE: in process utility: superuser finished set +SET test_oat_hooks.deny_object_access = true; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access] +NOTICE: in process utility: superuser finished set +SET test_oat_hooks.deny_exec_perms = true; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms] +NOTICE: in process utility: superuser finished set +SET test_oat_hooks.deny_utility_commands = true; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands] +NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands] +NOTICE: in process utility: superuser finished set +-- Try again as non-superuser with permisisons denied +SET SESSION AUTHORIZATION regress_test_user; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [session_authorization] +ERROR: permission denied: set session_authorization +SELECT * FROM regress_test_table; +NOTICE: in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed] +LINE 1: SELECT * FROM regress_test_table; + ^ +NOTICE: in object access: superuser finished namespace search (subId=0) [no report on violation, allowed] +LINE 1: SELECT * FROM regress_test_table; + ^ +NOTICE: in executor check perms: superuser attempting execute +NOTICE: in executor check perms: superuser finished execute + t +--- +(0 rows) + +SELECT regress_test_func('arg'); +NOTICE: in executor check perms: superuser attempting execute +NOTICE: in executor check perms: superuser finished execute + regress_test_func +------------------- + arg +(1 row) + +SET work_mem = 8192; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in process utility: superuser finished set +RESET work_mem; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in process utility: superuser finished set +ALTER SYSTEM SET work_mem = 8192; +NOTICE: in process utility: superuser attempting alter system +NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in process utility: superuser finished alter system +ALTER SYSTEM RESET work_mem; +NOTICE: in process utility: superuser attempting alter system +NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in process utility: superuser finished alter system +RESET SESSION AUTHORIZATION; +NOTICE: in process utility: superuser attempting set +NOTICE: in object_access_hook_str: superuser attempting alter (set) [session_authorization] +NOTICE: in object_access_hook_str: superuser finished alter (set) [session_authorization] +NOTICE: in process utility: superuser finished set +SET test_oat_hooks.audit = false; +NOTICE: in process utility: superuser attempting set +RESET client_min_messages; diff --git a/src/test/modules/test_oat_hooks/oat_hooks.conf b/src/test/modules/test_oat_hooks/oat_hooks.conf new file mode 100644 index 0000000000..a44cbdd4a4 --- /dev/null +++ b/src/test/modules/test_oat_hooks/oat_hooks.conf @@ -0,0 +1 @@ +shared_preload_libraries = test_oat_hooks diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql new file mode 100644 index 0000000000..86addbd670 --- /dev/null +++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql @@ -0,0 +1,59 @@ +LOAD 'test_oat_hooks'; + +-- Turn on logging messages to see audit messages +SET client_min_messages = 'LOG'; + +-- SET commands fire both the ProcessUtility_hook and the +-- object_access_hook_str. Since the auditing GUC starts out false, we miss the +-- initial "attempting" audit message from the ProcessUtility_hook, but we +-- should thereafter see the audit messages +SET test_oat_hooks.audit = true; + +-- Create objects for use in the test +CREATE USER regress_test_user; +CREATE TABLE regress_test_table (t text); +GRANT SELECT ON Table regress_test_table TO public; +CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$ + SELECT $1; +$$ LANGUAGE sql; +GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public; + +-- Do a few things as superuser +SELECT * FROM regress_test_table; +SELECT regress_test_func('arg'); +SET work_mem = 8192; +RESET work_mem; +ALTER SYSTEM SET work_mem = 8192; +ALTER SYSTEM RESET work_mem; + +-- Do those same things as non-superuser +SET SESSION AUTHORIZATION regress_test_user; +SELECT * FROM regress_test_table; +SELECT regress_test_func('arg'); +SET work_mem = 8192; +RESET work_mem; +ALTER SYSTEM SET work_mem = 8192; +ALTER SYSTEM RESET work_mem; +RESET SESSION AUTHORIZATION; + +-- Turn off non-superuser permissions +SET test_oat_hooks.deny_set_variable = true; +SET test_oat_hooks.deny_alter_system = true; +SET test_oat_hooks.deny_object_access = true; +SET test_oat_hooks.deny_exec_perms = true; +SET test_oat_hooks.deny_utility_commands = true; + +-- Try again as non-superuser with permisisons denied +SET SESSION AUTHORIZATION regress_test_user; +SELECT * FROM regress_test_table; +SELECT regress_test_func('arg'); +SET work_mem = 8192; +RESET work_mem; +ALTER SYSTEM SET work_mem = 8192; +ALTER SYSTEM RESET work_mem; + +RESET SESSION AUTHORIZATION; + +SET test_oat_hooks.audit = false; + +RESET client_min_messages; diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c new file mode 100644 index 0000000000..177be98a71 --- /dev/null +++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c @@ -0,0 +1,935 @@ +/*-------------------------------------------------------------------------- + * + * test_oat_hooks.c + * Code for testing mandatory access control (MAC) using object access hooks. + * + * Copyright (c) 2015-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_oat_hooks/test_oat_hooks.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/dependency.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_proc.h" +#include "executor/executor.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "tcop/utility.h" +#include "test_oat_hooks.h" + +PG_MODULE_MAGIC; + +/* + * GUCs controlling which operations to deny + */ +static bool REGRESS_deny_set_variable = false; +static bool REGRESS_deny_alter_system = false; +static bool REGRESS_deny_object_access = false; +static bool REGRESS_deny_exec_perms = false; +static bool REGRESS_deny_utility_commands = false; +static bool REGRESS_audit = false; + +/* Saved hook values in case of unload */ +static object_access_hook_type next_object_access_hook = NULL; +static object_access_hook_type_str next_object_access_hook_str = NULL; +static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; +static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; + +/* Test Object Access Type Hook hooks */ +static void REGRESS_object_access_hook_str(ObjectAccessType access, + Oid classId, const char *objName, + int subId, void *arg); +static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId, + Oid objectId, int subId, void *arg); +static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort); +static void REGRESS_utility_command(PlannedStmt *pstmt, + const char *queryString, bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *qc); + +/* Helper functions */ +static const char *nodetag_to_string(NodeTag tag); +static char *accesstype_to_string(ObjectAccessType access, int subId); +static char *accesstype_arg_to_string(ObjectAccessType access, void *arg); + + +void _PG_init(void); +void _PG_fini(void); + +/* + * Module load/unload callback + */ +void +_PG_init(void) +{ + /* + * We allow to load the Object Access Type test module on single-user-mode + * or shared_preload_libraries settings only. + */ + if (IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("test_oat_hooks must be loaded via shared_preload_libraries"))); + + /* + * test_oat_hooks.deny_set_variable = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.deny_set_variable", + "Deny non-superuser set permissions", + NULL, + ®RESS_deny_set_variable, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.deny_alter_system = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.deny_alter_system", + "Deny non-superuser alter system set permissions", + NULL, + ®RESS_deny_alter_system, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.deny_object_access = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.deny_object_access", + "Deny non-superuser object access permissions", + NULL, + ®RESS_deny_object_access, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.deny_exec_perms = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms", + "Deny non-superuser exec permissions", + NULL, + ®RESS_deny_exec_perms, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.deny_utility_commands = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands", + "Deny non-superuser utility commands", + NULL, + ®RESS_deny_utility_commands, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.audit = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.audit", + "Turn on/off debug audit messages", + NULL, + ®RESS_audit, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("test_oat_hooks"); + + /* Object access hook */ + next_object_access_hook = object_access_hook; + object_access_hook = REGRESS_object_access_hook; + + /* Object access hook str */ + next_object_access_hook_str = object_access_hook_str; + object_access_hook_str = REGRESS_object_access_hook_str; + + /* DML permission check */ + next_exec_check_perms_hook = ExecutorCheckPerms_hook; + ExecutorCheckPerms_hook = REGRESS_exec_check_perms; + + /* ProcessUtility hook */ + next_ProcessUtility_hook = ProcessUtility_hook; + ProcessUtility_hook = REGRESS_utility_command; +} + +void +_PG_fini(void) +{ + /* Unload hooks */ + if (object_access_hook == REGRESS_object_access_hook) + object_access_hook = next_object_access_hook; + + if (object_access_hook_str == REGRESS_object_access_hook_str) + object_access_hook_str = next_object_access_hook_str; + + if (ExecutorCheckPerms_hook == REGRESS_exec_check_perms) + ExecutorCheckPerms_hook = next_exec_check_perms_hook; + + if (ProcessUtility_hook == REGRESS_utility_command) + ProcessUtility_hook = next_ProcessUtility_hook; +} + +static void +emit_audit_message(const char *type, const char *hook, char *action, char *objName) +{ + if (REGRESS_audit) + { + const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser"; + + if (objName) + ereport(NOTICE, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName))); + else + ereport(NOTICE, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("in %s: %s %s %s", hook, who, type, action))); + } + + if (action) + pfree(action); + if (objName) + pfree(objName); +} + +static void +audit_attempt(const char *hook, char *action, char *objName) +{ + emit_audit_message("attempting", hook, action, objName); +} + +static void +audit_success(const char *hook, char *action, char *objName) +{ + emit_audit_message("finished", hook, action, objName); +} + +static void +audit_failure(const char *hook, char *action, char *objName) +{ + emit_audit_message("denied", hook, action, objName); +} + +static void +REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg) +{ + audit_attempt("object_access_hook_str", + accesstype_to_string(access, subId), + pstrdup(objName)); + + if (next_object_access_hook_str) + { + (*next_object_access_hook_str)(access, classId, objName, subId, arg); + } + + switch (access) + { + case OAT_POST_ALTER: + if (subId & ACL_SET_VALUE) + { + if (REGRESS_deny_set_variable && !superuser_arg(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: set %s", objName))); + } + else if (subId & ACL_ALTER_SYSTEM) + { + if (REGRESS_deny_alter_system && !superuser_arg(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: alter system set %s", objName))); + } + else + elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId); + break; + default: + break; + } + + audit_success("object_access_hook_str", + accesstype_to_string(access, subId), + pstrdup(objName)); +} + +static void +REGRESS_object_access_hook (ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg) +{ + audit_attempt("object access", + accesstype_to_string(access, 0), + accesstype_arg_to_string(access, arg)); + + if (REGRESS_deny_object_access && !superuser_arg(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: %s [%s]", + accesstype_to_string(access, 0), + accesstype_arg_to_string(access, arg)))); + + /* Forward to next hook in the chain */ + if (next_object_access_hook) + (*next_object_access_hook)(access, classId, objectId, subId, arg); + + audit_success("object access", + accesstype_to_string(access, 0), + accesstype_arg_to_string(access, arg)); +} + +static bool +REGRESS_exec_check_perms(List *rangeTabls, bool do_abort) +{ + bool am_super = superuser_arg(GetUserId()); + bool allow = true; + + audit_attempt("executor check perms", pstrdup("execute"), NULL); + + /* Perform our check */ + allow = !REGRESS_deny_exec_perms || am_super; + if (do_abort && !allow) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: %s", "execute"))); + + /* Forward to next hook in the chain */ + if (next_exec_check_perms_hook && + !(*next_exec_check_perms_hook) (rangeTabls, do_abort)) + allow = false; + + if (allow) + audit_success("executor check perms", + pstrdup("execute"), + NULL); + else + audit_failure("executor check perms", + pstrdup("execute"), + NULL); + + return allow; +} + +static void +REGRESS_utility_command(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + + const char *action; + NodeTag tag = nodeTag(parsetree); + + switch (tag) + { + case T_VariableSetStmt: + action = "set"; + break; + case T_AlterSystemStmt: + action = "alter system"; + break; + case T_LoadStmt: + action = "load"; + break; + default: + action = nodetag_to_string(tag); + break; + } + + audit_attempt("process utility", + pstrdup(action), + NULL); + + /* Check permissions */ + if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: %s", action))); + + /* Forward to next hook in the chain */ + if (next_ProcessUtility_hook) + (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + + /* We're done */ + audit_success("process utility", + pstrdup(action), + NULL); +} + +static const char * +nodetag_to_string(NodeTag tag) +{ + switch (tag) + { + case T_Invalid: return "Invalid"; break; + case T_IndexInfo: return "IndexInfo"; break; + case T_ExprContext: return "ExprContext"; break; + case T_ProjectionInfo: return "ProjectionInfo"; break; + case T_JunkFilter: return "JunkFilter"; break; + case T_OnConflictSetState: return "OnConflictSetState"; break; + case T_ResultRelInfo: return "ResultRelInfo"; break; + case T_EState: return "EState"; break; + case T_TupleTableSlot: return "TupleTableSlot"; break; + case T_Plan: return "Plan"; break; + case T_Result: return "Result"; break; + case T_ProjectSet: return "ProjectSet"; break; + case T_ModifyTable: return "ModifyTable"; break; + case T_Append: return "Append"; break; + case T_MergeAppend: return "MergeAppend"; break; + case T_RecursiveUnion: return "RecursiveUnion"; break; + case T_BitmapAnd: return "BitmapAnd"; break; + case T_BitmapOr: return "BitmapOr"; break; + case T_Scan: return "Scan"; break; + case T_SeqScan: return "SeqScan"; break; + case T_SampleScan: return "SampleScan"; break; + case T_IndexScan: return "IndexScan"; break; + case T_IndexOnlyScan: return "IndexOnlyScan"; break; + case T_BitmapIndexScan: return "BitmapIndexScan"; break; + case T_BitmapHeapScan: return "BitmapHeapScan"; break; + case T_TidScan: return "TidScan"; break; + case T_TidRangeScan: return "TidRangeScan"; break; + case T_SubqueryScan: return "SubqueryScan"; break; + case T_FunctionScan: return "FunctionScan"; break; + case T_ValuesScan: return "ValuesScan"; break; + case T_TableFuncScan: return "TableFuncScan"; break; + case T_CteScan: return "CteScan"; break; + case T_NamedTuplestoreScan: return "NamedTuplestoreScan"; break; + case T_WorkTableScan: return "WorkTableScan"; break; + case T_ForeignScan: return "ForeignScan"; break; + case T_CustomScan: return "CustomScan"; break; + case T_Join: return "Join"; break; + case T_NestLoop: return "NestLoop"; break; + case T_MergeJoin: return "MergeJoin"; break; + case T_HashJoin: return "HashJoin"; break; + case T_Material: return "Material"; break; + case T_Memoize: return "Memoize"; break; + case T_Sort: return "Sort"; break; + case T_IncrementalSort: return "IncrementalSort"; break; + case T_Group: return "Group"; break; + case T_Agg: return "Agg"; break; + case T_WindowAgg: return "WindowAgg"; break; + case T_Unique: return "Unique"; break; + case T_Gather: return "Gather"; break; + case T_GatherMerge: return "GatherMerge"; break; + case T_Hash: return "Hash"; break; + case T_SetOp: return "SetOp"; break; + case T_LockRows: return "LockRows"; break; + case T_Limit: return "Limit"; break; + case T_NestLoopParam: return "NestLoopParam"; break; + case T_PlanRowMark: return "PlanRowMark"; break; + case T_PartitionPruneInfo: return "PartitionPruneInfo"; break; + case T_PartitionedRelPruneInfo: return "PartitionedRelPruneInfo"; break; + case T_PartitionPruneStepOp: return "PartitionPruneStepOp"; break; + case T_PartitionPruneStepCombine: return "PartitionPruneStepCombine"; break; + case T_PlanInvalItem: return "PlanInvalItem"; break; + case T_PlanState: return "PlanState"; break; + case T_ResultState: return "ResultState"; break; + case T_ProjectSetState: return "ProjectSetState"; break; + case T_ModifyTableState: return "ModifyTableState"; break; + case T_AppendState: return "AppendState"; break; + case T_MergeAppendState: return "MergeAppendState"; break; + case T_RecursiveUnionState: return "RecursiveUnionState"; break; + case T_BitmapAndState: return "BitmapAndState"; break; + case T_BitmapOrState: return "BitmapOrState"; break; + case T_ScanState: return "ScanState"; break; + case T_SeqScanState: return "SeqScanState"; break; + case T_SampleScanState: return "SampleScanState"; break; + case T_IndexScanState: return "IndexScanState"; break; + case T_IndexOnlyScanState: return "IndexOnlyScanState"; break; + case T_BitmapIndexScanState: return "BitmapIndexScanState"; break; + case T_BitmapHeapScanState: return "BitmapHeapScanState"; break; + case T_TidScanState: return "TidScanState"; break; + case T_TidRangeScanState: return "TidRangeScanState"; break; + case T_SubqueryScanState: return "SubqueryScanState"; break; + case T_FunctionScanState: return "FunctionScanState"; break; + case T_TableFuncScanState: return "TableFuncScanState"; break; + case T_ValuesScanState: return "ValuesScanState"; break; + case T_CteScanState: return "CteScanState"; break; + case T_NamedTuplestoreScanState: return "NamedTuplestoreScanState"; break; + case T_WorkTableScanState: return "WorkTableScanState"; break; + case T_ForeignScanState: return "ForeignScanState"; break; + case T_CustomScanState: return "CustomScanState"; break; + case T_JoinState: return "JoinState"; break; + case T_NestLoopState: return "NestLoopState"; break; + case T_MergeJoinState: return "MergeJoinState"; break; + case T_HashJoinState: return "HashJoinState"; break; + case T_MaterialState: return "MaterialState"; break; + case T_MemoizeState: return "MemoizeState"; break; + case T_SortState: return "SortState"; break; + case T_IncrementalSortState: return "IncrementalSortState"; break; + case T_GroupState: return "GroupState"; break; + case T_AggState: return "AggState"; break; + case T_WindowAggState: return "WindowAggState"; break; + case T_UniqueState: return "UniqueState"; break; + case T_GatherState: return "GatherState"; break; + case T_GatherMergeState: return "GatherMergeState"; break; + case T_HashState: return "HashState"; break; + case T_SetOpState: return "SetOpState"; break; + case T_LockRowsState: return "LockRowsState"; break; + case T_LimitState: return "LimitState"; break; + case T_Alias: return "Alias"; break; + case T_RangeVar: return "RangeVar"; break; + case T_TableFunc: return "TableFunc"; break; + case T_Var: return "Var"; break; + case T_Const: return "Const"; break; + case T_Param: return "Param"; break; + case T_Aggref: return "Aggref"; break; + case T_GroupingFunc: return "GroupingFunc"; break; + case T_WindowFunc: return "WindowFunc"; break; + case T_SubscriptingRef: return "SubscriptingRef"; break; + case T_FuncExpr: return "FuncExpr"; break; + case T_NamedArgExpr: return "NamedArgExpr"; break; + case T_OpExpr: return "OpExpr"; break; + case T_DistinctExpr: return "DistinctExpr"; break; + case T_NullIfExpr: return "NullIfExpr"; break; + case T_ScalarArrayOpExpr: return "ScalarArrayOpExpr"; break; + case T_BoolExpr: return "BoolExpr"; break; + case T_SubLink: return "SubLink"; break; + case T_SubPlan: return "SubPlan"; break; + case T_AlternativeSubPlan: return "AlternativeSubPlan"; break; + case T_FieldSelect: return "FieldSelect"; break; + case T_FieldStore: return "FieldStore"; break; + case T_RelabelType: return "RelabelType"; break; + case T_CoerceViaIO: return "CoerceViaIO"; break; + case T_ArrayCoerceExpr: return "ArrayCoerceExpr"; break; + case T_ConvertRowtypeExpr: return "ConvertRowtypeExpr"; break; + case T_CollateExpr: return "CollateExpr"; break; + case T_CaseExpr: return "CaseExpr"; break; + case T_CaseWhen: return "CaseWhen"; break; + case T_CaseTestExpr: return "CaseTestExpr"; break; + case T_ArrayExpr: return "ArrayExpr"; break; + case T_RowExpr: return "RowExpr"; break; + case T_RowCompareExpr: return "RowCompareExpr"; break; + case T_CoalesceExpr: return "CoalesceExpr"; break; + case T_MinMaxExpr: return "MinMaxExpr"; break; + case T_SQLValueFunction: return "SQLValueFunction"; break; + case T_XmlExpr: return "XmlExpr"; break; + case T_NullTest: return "NullTest"; break; + case T_BooleanTest: return "BooleanTest"; break; + case T_CoerceToDomain: return "CoerceToDomain"; break; + case T_CoerceToDomainValue: return "CoerceToDomainValue"; break; + case T_SetToDefault: return "SetToDefault"; break; + case T_CurrentOfExpr: return "CurrentOfExpr"; break; + case T_NextValueExpr: return "NextValueExpr"; break; + case T_InferenceElem: return "InferenceElem"; break; + case T_TargetEntry: return "TargetEntry"; break; + case T_RangeTblRef: return "RangeTblRef"; break; + case T_JoinExpr: return "JoinExpr"; break; + case T_FromExpr: return "FromExpr"; break; + case T_OnConflictExpr: return "OnConflictExpr"; break; + case T_IntoClause: return "IntoClause"; break; + case T_ExprState: return "ExprState"; break; + case T_WindowFuncExprState: return "WindowFuncExprState"; break; + case T_SetExprState: return "SetExprState"; break; + case T_SubPlanState: return "SubPlanState"; break; + case T_DomainConstraintState: return "DomainConstraintState"; break; + case T_PlannerInfo: return "PlannerInfo"; break; + case T_PlannerGlobal: return "PlannerGlobal"; break; + case T_RelOptInfo: return "RelOptInfo"; break; + case T_IndexOptInfo: return "IndexOptInfo"; break; + case T_ForeignKeyOptInfo: return "ForeignKeyOptInfo"; break; + case T_ParamPathInfo: return "ParamPathInfo"; break; + case T_Path: return "Path"; break; + case T_IndexPath: return "IndexPath"; break; + case T_BitmapHeapPath: return "BitmapHeapPath"; break; + case T_BitmapAndPath: return "BitmapAndPath"; break; + case T_BitmapOrPath: return "BitmapOrPath"; break; + case T_TidPath: return "TidPath"; break; + case T_TidRangePath: return "TidRangePath"; break; + case T_SubqueryScanPath: return "SubqueryScanPath"; break; + case T_ForeignPath: return "ForeignPath"; break; + case T_CustomPath: return "CustomPath"; break; + case T_NestPath: return "NestPath"; break; + case T_MergePath: return "MergePath"; break; + case T_HashPath: return "HashPath"; break; + case T_AppendPath: return "AppendPath"; break; + case T_MergeAppendPath: return "MergeAppendPath"; break; + case T_GroupResultPath: return "GroupResultPath"; break; + case T_MaterialPath: return "MaterialPath"; break; + case T_MemoizePath: return "MemoizePath"; break; + case T_UniquePath: return "UniquePath"; break; + case T_GatherPath: return "GatherPath"; break; + case T_GatherMergePath: return "GatherMergePath"; break; + case T_ProjectionPath: return "ProjectionPath"; break; + case T_ProjectSetPath: return "ProjectSetPath"; break; + case T_SortPath: return "SortPath"; break; + case T_IncrementalSortPath: return "IncrementalSortPath"; break; + case T_GroupPath: return "GroupPath"; break; + case T_UpperUniquePath: return "UpperUniquePath"; break; + case T_AggPath: return "AggPath"; break; + case T_GroupingSetsPath: return "GroupingSetsPath"; break; + case T_MinMaxAggPath: return "MinMaxAggPath"; break; + case T_WindowAggPath: return "WindowAggPath"; break; + case T_SetOpPath: return "SetOpPath"; break; + case T_RecursiveUnionPath: return "RecursiveUnionPath"; break; + case T_LockRowsPath: return "LockRowsPath"; break; + case T_ModifyTablePath: return "ModifyTablePath"; break; + case T_LimitPath: return "LimitPath"; break; + case T_EquivalenceClass: return "EquivalenceClass"; break; + case T_EquivalenceMember: return "EquivalenceMember"; break; + case T_PathKey: return "PathKey"; break; + case T_PathTarget: return "PathTarget"; break; + case T_RestrictInfo: return "RestrictInfo"; break; + case T_IndexClause: return "IndexClause"; break; + case T_PlaceHolderVar: return "PlaceHolderVar"; break; + case T_SpecialJoinInfo: return "SpecialJoinInfo"; break; + case T_AppendRelInfo: return "AppendRelInfo"; break; + case T_RowIdentityVarInfo: return "RowIdentityVarInfo"; break; + case T_PlaceHolderInfo: return "PlaceHolderInfo"; break; + case T_MinMaxAggInfo: return "MinMaxAggInfo"; break; + case T_PlannerParamItem: return "PlannerParamItem"; break; + case T_RollupData: return "RollupData"; break; + case T_GroupingSetData: return "GroupingSetData"; break; + case T_StatisticExtInfo: return "StatisticExtInfo"; break; + case T_AllocSetContext: return "AllocSetContext"; break; + case T_SlabContext: return "SlabContext"; break; + case T_GenerationContext: return "GenerationContext"; break; + case T_Integer: return "Integer"; break; + case T_Float: return "Float"; break; + case T_Boolean: return "Boolean"; break; + case T_String: return "String"; break; + case T_BitString: return "BitString"; break; + case T_List: return "List"; break; + case T_IntList: return "IntList"; break; + case T_OidList: return "OidList"; break; + case T_ExtensibleNode: return "ExtensibleNode"; break; + case T_RawStmt: return "RawStmt"; break; + case T_Query: return "Query"; break; + case T_PlannedStmt: return "PlannedStmt"; break; + case T_InsertStmt: return "InsertStmt"; break; + case T_DeleteStmt: return "DeleteStmt"; break; + case T_UpdateStmt: return "UpdateStmt"; break; + case T_SelectStmt: return "SelectStmt"; break; + case T_ReturnStmt: return "ReturnStmt"; break; + case T_PLAssignStmt: return "PLAssignStmt"; break; + case T_AlterTableStmt: return "AlterTableStmt"; break; + case T_AlterTableCmd: return "AlterTableCmd"; break; + case T_AlterDomainStmt: return "AlterDomainStmt"; break; + case T_SetOperationStmt: return "SetOperationStmt"; break; + case T_GrantStmt: return "GrantStmt"; break; + case T_GrantRoleStmt: return "GrantRoleStmt"; break; + case T_AlterDefaultPrivilegesStmt: return "AlterDefaultPrivilegesStmt"; break; + case T_ClosePortalStmt: return "ClosePortalStmt"; break; + case T_ClusterStmt: return "ClusterStmt"; break; + case T_CopyStmt: return "CopyStmt"; break; + case T_CreateStmt: return "CreateStmt"; break; + case T_DefineStmt: return "DefineStmt"; break; + case T_DropStmt: return "DropStmt"; break; + case T_TruncateStmt: return "TruncateStmt"; break; + case T_CommentStmt: return "CommentStmt"; break; + case T_FetchStmt: return "FetchStmt"; break; + case T_IndexStmt: return "IndexStmt"; break; + case T_CreateFunctionStmt: return "CreateFunctionStmt"; break; + case T_AlterFunctionStmt: return "AlterFunctionStmt"; break; + case T_DoStmt: return "DoStmt"; break; + case T_RenameStmt: return "RenameStmt"; break; + case T_RuleStmt: return "RuleStmt"; break; + case T_NotifyStmt: return "NotifyStmt"; break; + case T_ListenStmt: return "ListenStmt"; break; + case T_UnlistenStmt: return "UnlistenStmt"; break; + case T_TransactionStmt: return "TransactionStmt"; break; + case T_ViewStmt: return "ViewStmt"; break; + case T_LoadStmt: return "LoadStmt"; break; + case T_CreateDomainStmt: return "CreateDomainStmt"; break; + case T_CreatedbStmt: return "CreatedbStmt"; break; + case T_DropdbStmt: return "DropdbStmt"; break; + case T_VacuumStmt: return "VacuumStmt"; break; + case T_ExplainStmt: return "ExplainStmt"; break; + case T_CreateTableAsStmt: return "CreateTableAsStmt"; break; + case T_CreateSeqStmt: return "CreateSeqStmt"; break; + case T_AlterSeqStmt: return "AlterSeqStmt"; break; + case T_VariableSetStmt: return "VariableSetStmt"; break; + case T_VariableShowStmt: return "VariableShowStmt"; break; + case T_DiscardStmt: return "DiscardStmt"; break; + case T_CreateTrigStmt: return "CreateTrigStmt"; break; + case T_CreatePLangStmt: return "CreatePLangStmt"; break; + case T_CreateRoleStmt: return "CreateRoleStmt"; break; + case T_AlterRoleStmt: return "AlterRoleStmt"; break; + case T_DropRoleStmt: return "DropRoleStmt"; break; + case T_LockStmt: return "LockStmt"; break; + case T_ConstraintsSetStmt: return "ConstraintsSetStmt"; break; + case T_ReindexStmt: return "ReindexStmt"; break; + case T_CheckPointStmt: return "CheckPointStmt"; break; + case T_CreateSchemaStmt: return "CreateSchemaStmt"; break; + case T_AlterDatabaseStmt: return "AlterDatabaseStmt"; break; + case T_AlterDatabaseRefreshCollStmt: return "AlterDatabaseRefreshCollStmt"; break; + case T_AlterDatabaseSetStmt: return "AlterDatabaseSetStmt"; break; + case T_AlterRoleSetStmt: return "AlterRoleSetStmt"; break; + case T_CreateConversionStmt: return "CreateConversionStmt"; break; + case T_CreateCastStmt: return "CreateCastStmt"; break; + case T_CreateOpClassStmt: return "CreateOpClassStmt"; break; + case T_CreateOpFamilyStmt: return "CreateOpFamilyStmt"; break; + case T_AlterOpFamilyStmt: return "AlterOpFamilyStmt"; break; + case T_PrepareStmt: return "PrepareStmt"; break; + case T_ExecuteStmt: return "ExecuteStmt"; break; + case T_DeallocateStmt: return "DeallocateStmt"; break; + case T_DeclareCursorStmt: return "DeclareCursorStmt"; break; + case T_CreateTableSpaceStmt: return "CreateTableSpaceStmt"; break; + case T_DropTableSpaceStmt: return "DropTableSpaceStmt"; break; + case T_AlterObjectDependsStmt: return "AlterObjectDependsStmt"; break; + case T_AlterObjectSchemaStmt: return "AlterObjectSchemaStmt"; break; + case T_AlterOwnerStmt: return "AlterOwnerStmt"; break; + case T_AlterOperatorStmt: return "AlterOperatorStmt"; break; + case T_AlterTypeStmt: return "AlterTypeStmt"; break; + case T_DropOwnedStmt: return "DropOwnedStmt"; break; + case T_ReassignOwnedStmt: return "ReassignOwnedStmt"; break; + case T_CompositeTypeStmt: return "CompositeTypeStmt"; break; + case T_CreateEnumStmt: return "CreateEnumStmt"; break; + case T_CreateRangeStmt: return "CreateRangeStmt"; break; + case T_AlterEnumStmt: return "AlterEnumStmt"; break; + case T_AlterTSDictionaryStmt: return "AlterTSDictionaryStmt"; break; + case T_AlterTSConfigurationStmt: return "AlterTSConfigurationStmt"; break; + case T_CreateFdwStmt: return "CreateFdwStmt"; break; + case T_AlterFdwStmt: return "AlterFdwStmt"; break; + case T_CreateForeignServerStmt: return "CreateForeignServerStmt"; break; + case T_AlterForeignServerStmt: return "AlterForeignServerStmt"; break; + case T_CreateUserMappingStmt: return "CreateUserMappingStmt"; break; + case T_AlterUserMappingStmt: return "AlterUserMappingStmt"; break; + case T_DropUserMappingStmt: return "DropUserMappingStmt"; break; + case T_AlterTableSpaceOptionsStmt: return "AlterTableSpaceOptionsStmt"; break; + case T_AlterTableMoveAllStmt: return "AlterTableMoveAllStmt"; break; + case T_SecLabelStmt: return "SecLabelStmt"; break; + case T_CreateForeignTableStmt: return "CreateForeignTableStmt"; break; + case T_ImportForeignSchemaStmt: return "ImportForeignSchemaStmt"; break; + case T_CreateExtensionStmt: return "CreateExtensionStmt"; break; + case T_AlterExtensionStmt: return "AlterExtensionStmt"; break; + case T_AlterExtensionContentsStmt: return "AlterExtensionContentsStmt"; break; + case T_CreateEventTrigStmt: return "CreateEventTrigStmt"; break; + case T_AlterEventTrigStmt: return "AlterEventTrigStmt"; break; + case T_RefreshMatViewStmt: return "RefreshMatViewStmt"; break; + case T_ReplicaIdentityStmt: return "ReplicaIdentityStmt"; break; + case T_AlterSystemStmt: return "AlterSystemStmt"; break; + case T_CreatePolicyStmt: return "CreatePolicyStmt"; break; + case T_AlterPolicyStmt: return "AlterPolicyStmt"; break; + case T_CreateTransformStmt: return "CreateTransformStmt"; break; + case T_CreateAmStmt: return "CreateAmStmt"; break; + case T_CreatePublicationStmt: return "CreatePublicationStmt"; break; + case T_AlterPublicationStmt: return "AlterPublicationStmt"; break; + case T_CreateSubscriptionStmt: return "CreateSubscriptionStmt"; break; + case T_AlterSubscriptionStmt: return "AlterSubscriptionStmt"; break; + case T_DropSubscriptionStmt: return "DropSubscriptionStmt"; break; + case T_CreateStatsStmt: return "CreateStatsStmt"; break; + case T_AlterCollationStmt: return "AlterCollationStmt"; break; + case T_CallStmt: return "CallStmt"; break; + case T_AlterStatsStmt: return "AlterStatsStmt"; break; + case T_A_Expr: return "A_Expr"; break; + case T_ColumnRef: return "ColumnRef"; break; + case T_ParamRef: return "ParamRef"; break; + case T_A_Const: return "A_Const"; break; + case T_FuncCall: return "FuncCall"; break; + case T_A_Star: return "A_Star"; break; + case T_A_Indices: return "A_Indices"; break; + case T_A_Indirection: return "A_Indirection"; break; + case T_A_ArrayExpr: return "A_ArrayExpr"; break; + case T_ResTarget: return "ResTarget"; break; + case T_MultiAssignRef: return "MultiAssignRef"; break; + case T_TypeCast: return "TypeCast"; break; + case T_CollateClause: return "CollateClause"; break; + case T_SortBy: return "SortBy"; break; + case T_WindowDef: return "WindowDef"; break; + case T_RangeSubselect: return "RangeSubselect"; break; + case T_RangeFunction: return "RangeFunction"; break; + case T_RangeTableSample: return "RangeTableSample"; break; + case T_RangeTableFunc: return "RangeTableFunc"; break; + case T_RangeTableFuncCol: return "RangeTableFuncCol"; break; + case T_TypeName: return "TypeName"; break; + case T_ColumnDef: return "ColumnDef"; break; + case T_IndexElem: return "IndexElem"; break; + case T_StatsElem: return "StatsElem"; break; + case T_Constraint: return "Constraint"; break; + case T_DefElem: return "DefElem"; break; + case T_RangeTblEntry: return "RangeTblEntry"; break; + case T_RangeTblFunction: return "RangeTblFunction"; break; + case T_TableSampleClause: return "TableSampleClause"; break; + case T_WithCheckOption: return "WithCheckOption"; break; + case T_SortGroupClause: return "SortGroupClause"; break; + case T_GroupingSet: return "GroupingSet"; break; + case T_WindowClause: return "WindowClause"; break; + case T_ObjectWithArgs: return "ObjectWithArgs"; break; + case T_AccessPriv: return "AccessPriv"; break; + case T_CreateOpClassItem: return "CreateOpClassItem"; break; + case T_TableLikeClause: return "TableLikeClause"; break; + case T_FunctionParameter: return "FunctionParameter"; break; + case T_LockingClause: return "LockingClause"; break; + case T_RowMarkClause: return "RowMarkClause"; break; + case T_XmlSerialize: return "XmlSerialize"; break; + case T_WithClause: return "WithClause"; break; + case T_InferClause: return "InferClause"; break; + case T_OnConflictClause: return "OnConflictClause"; break; + case T_CTESearchClause: return "CTESearchClause"; break; + case T_CTECycleClause: return "CTECycleClause"; break; + case T_CommonTableExpr: return "CommonTableExpr"; break; + case T_RoleSpec: return "RoleSpec"; break; + case T_TriggerTransition: return "TriggerTransition"; break; + case T_PartitionElem: return "PartitionElem"; break; + case T_PartitionSpec: return "PartitionSpec"; break; + case T_PartitionBoundSpec: return "PartitionBoundSpec"; break; + case T_PartitionRangeDatum: return "PartitionRangeDatum"; break; + case T_PartitionCmd: return "PartitionCmd"; break; + case T_VacuumRelation: return "VacuumRelation"; break; + case T_PublicationObjSpec: return "PublicationObjSpec"; break; + case T_PublicationTable: return "PublicationTable"; break; + case T_IdentifySystemCmd: return "IdentifySystemCmd"; break; + case T_BaseBackupCmd: return "BaseBackupCmd"; break; + case T_CreateReplicationSlotCmd: return "CreateReplicationSlotCmd"; break; + case T_DropReplicationSlotCmd: return "DropReplicationSlotCmd"; break; + case T_ReadReplicationSlotCmd: return "ReadReplicationSlotCmd"; break; + case T_StartReplicationCmd: return "StartReplicationCmd"; break; + case T_TimeLineHistoryCmd: return "TimeLineHistoryCmd"; break; + case T_TriggerData: return "TriggerData"; break; + case T_EventTriggerData: return "EventTriggerData"; break; + case T_ReturnSetInfo: return "ReturnSetInfo"; break; + case T_WindowObjectData: return "WindowObjectData"; break; + case T_TIDBitmap: return "TIDBitmap"; break; + case T_InlineCodeBlock: return "InlineCodeBlock"; break; + case T_FdwRoutine: return "FdwRoutine"; break; + case T_IndexAmRoutine: return "IndexAmRoutine"; break; + case T_TableAmRoutine: return "TableAmRoutine"; break; + case T_TsmRoutine: return "TsmRoutine"; break; + case T_ForeignKeyCacheInfo: return "ForeignKeyCacheInfo"; break; + case T_CallContext: return "CallContext"; break; + case T_SupportRequestSimplify: return "SupportRequestSimplify"; break; + case T_SupportRequestSelectivity: return "SupportRequestSelectivity"; break; + case T_SupportRequestCost: return "SupportRequestCost"; break; + case T_SupportRequestRows: return "SupportRequestRows"; break; + case T_SupportRequestIndexCondition: return "SupportRequestIndexCondition"; break; + default: + break; + } + return "UNRECOGNIZED NodeTag"; +} + +static char * +accesstype_to_string(ObjectAccessType access, int subId) +{ + const char *type; + + switch (access) + { + case OAT_POST_CREATE: + type = "create"; + break; + case OAT_DROP: + type = "drop"; + break; + case OAT_POST_ALTER: + type = "alter"; + break; + case OAT_NAMESPACE_SEARCH: + type = "namespace search"; + break; + case OAT_FUNCTION_EXECUTE: + type = "execute"; + break; + case OAT_TRUNCATE: + type = "truncate"; + break; + default: + type = "UNRECOGNIZED ObjectAccessType"; + } + + if (subId & ACL_SET_VALUE) + return psprintf("%s (set)", type); + if (subId & ACL_ALTER_SYSTEM) + return psprintf("%s (alter system set)", type); + + return psprintf("%s (subId=%d)", type, subId); +} + +static char * +accesstype_arg_to_string(ObjectAccessType access, void *arg) +{ + if (arg == NULL) + return pstrdup("extra info null"); + + switch (access) + { + case OAT_POST_CREATE: + { + ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *)arg; + return pstrdup(pc_arg->is_internal ? "internal" : "explicit"); + } + break; + case OAT_DROP: + { + ObjectAccessDrop *drop_arg = (ObjectAccessDrop *)arg; + + return psprintf("%s%s%s%s%s%s", + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "internal action," : ""), + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "concurrent drop," : ""), + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "suppress notices," : ""), + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "keep original object," : ""), + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "keep extensions," : ""), + ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) + ? "normal concurrent drop," : "")); + } + break; + case OAT_POST_ALTER: + { + ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter*)arg; + + return psprintf("%s %s auxiliary object", + (pa_arg->is_internal ? "internal" : "explicit"), + (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without")); + } + break; + case OAT_NAMESPACE_SEARCH: + { + ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *)arg; + + return psprintf("%s, %s", + (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"), + (ns_arg->result ? "allowed" : "denied")); + } + break; + case OAT_TRUNCATE: + case OAT_FUNCTION_EXECUTE: + /* hook takes no arg. */ + return pstrdup("unexpected extra info pointer received"); + default: + return pstrdup("cannot parse extra info for unrecognized access type"); + } + + return pstrdup("unknown"); +} diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.control b/src/test/modules/test_oat_hooks/test_oat_hooks.control new file mode 100644 index 0000000000..3d3cf4a8ac --- /dev/null +++ b/src/test/modules/test_oat_hooks/test_oat_hooks.control @@ -0,0 +1,4 @@ +comment = 'Test code for Object Access hooks' +default_version = '1.0' +module_pathname = '$libdir/test_oat_hooks' +relocatable = true diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.h b/src/test/modules/test_oat_hooks/test_oat_hooks.h new file mode 100644 index 0000000000..ff1ee1bf5c --- /dev/null +++ b/src/test/modules/test_oat_hooks/test_oat_hooks.h @@ -0,0 +1,25 @@ +/*-------------------------------------------------------------------------- + * + * test_rls_hooks.h + * Definitions for OAT hooks + * + * Copyright (c) 2015-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_oat_hooks/test_oat_hooks.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef TEST_OAT_HOOKS_H +#define TEST_OAT_HOOKS_H + +#include <rewrite/rowsecurity.h> + +/* Return set of permissive hooks based on CmdType and Relation */ +extern List *test_rls_hooks_permissive(CmdType cmdtype, Relation relation); + +/* Return set of restrictive hooks based on CmdType and Relation */ +extern List *test_rls_hooks_restrictive(CmdType cmdtype, Relation relation); + +#endif /* TEST_OAT_HOOKS_H */ -- 2.35.1 ^ permalink raw reply [nested|flat] 2+ messages in thread
end of thread, other threads:[~2022-03-18 03:21 UTC | newest] Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-01-20 21:16 [PATCH v6 2/3] Make resowners more easily extensible. Heikki Linnakangas <[email protected]> 2022-03-18 03:21 New Object Access Type hooks Mark Dilger <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox