From 6ede13d1b5f515df0a199a7a830e448dab1511c0 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Fri, 5 Sep 2025 08:18:18 +0100 Subject: [PATCH 01/96] Fix concurrent update issue with MERGE. When executing a MERGE UPDATE action, if there is more than one concurrent update of the target row, the lock-and-retry code would sometimes incorrectly identify the latest version of the target tuple, leading to incorrect results. This was caused by using the ctid field from the TM_FailureData returned by table_tuple_lock() in a case where the result was TM_Ok, which is unsafe because the TM_FailureData struct is not guaranteed to be fully populated in that case. Instead, it should use the tupleid passed to (and updated by) table_tuple_lock(). To reduce the chances of similar errors in the future, improve the commentary for table_tuple_lock() and TM_FailureData to make it clearer that table_tuple_lock() updates the tid passed to it, and most fields of TM_FailureData should not be relied on in non-failure cases. An exception to this is the "traversed" field, which is set in both success and failure cases. Reported-by: Dmitry Author: Yugo Nagata Reviewed-by: Dean Rasheed Reviewed-by: Chao Li Discussion: https://postgr.es/m/1570d30e-2b95-4239-b9c3-f7bf2f2f8556@yandex.ru Backpatch-through: 15 --- src/backend/executor/nodeModifyTable.c | 9 +- src/include/access/tableam.h | 15 +- .../expected/merge-match-recheck.out | 145 ++++++++++++++++++ .../isolation/specs/merge-match-recheck.spec | 18 +++ 4 files changed, 179 insertions(+), 8 deletions(-) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index b0c4e2c0d32a4..4c5647ac38a1c 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3402,7 +3402,7 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * the tuple moved, and setting our current * resultRelInfo to that. */ - if (ItemPointerIndicatesMovedPartitions(&context->tmfd.ctid)) + if (ItemPointerIndicatesMovedPartitions(tupleid)) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be merged was already moved to another partition due to concurrent update"))); @@ -3450,12 +3450,13 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, if (ItemPointerIsValid(&lockedtid)) UnlockTuple(resultRelInfo->ri_RelationDesc, &lockedtid, InplaceUpdateTupleLock); - LockTuple(resultRelInfo->ri_RelationDesc, &context->tmfd.ctid, + LockTuple(resultRelInfo->ri_RelationDesc, tupleid, InplaceUpdateTupleLock); - lockedtid = context->tmfd.ctid; + lockedtid = *tupleid; } + if (!table_tuple_fetch_row_version(resultRelationDesc, - &context->tmfd.ctid, + tupleid, SnapshotAny, resultRelInfo->ri_oldTupleSlot)) elog(ERROR, "failed to fetch the target tuple"); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 1c9e802a6b128..b2ce35e2a3407 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -121,7 +121,9 @@ typedef enum TU_UpdateIndexes /* * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail * because the target tuple is already outdated, they fill in this struct to - * provide information to the caller about what happened. + * provide information to the caller about what happened. When those functions + * succeed, the contents of this struct should not be relied upon, except for + * `traversed`, which may be set in both success and failure cases. * * ctid is the target's ctid link: it is the same as the target's TID if the * target was deleted, or the location of the replacement tuple if the target @@ -137,6 +139,9 @@ typedef enum TU_UpdateIndexes * tuple); otherwise cmax is zero. (We make this restriction because * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other * transactions.) + * + * traversed indicates if an update chain was followed in order to try to lock + * the target tuple. (This may be set in both success and failure cases.) */ typedef struct TM_FailureData { @@ -1508,7 +1513,7 @@ table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, * * Input parameters: * relation: relation containing tuple (caller must hold suitable lock) - * tid: TID of tuple to lock + * tid: TID of tuple to lock (updated if an update chain was followed) * snapshot: snapshot to use for visibility determinations * cid: current command ID (used for visibility test, and stored into * tuple's cmax if lock is successful) @@ -1533,8 +1538,10 @@ table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, * TM_WouldBlock: lock couldn't be acquired and wait_policy is skip * * In the failure cases other than TM_Invisible and TM_Deleted, the routine - * fills *tmfd with the tuple's t_ctid, t_xmax, and, if possible, t_cmax. See - * comments for struct TM_FailureData for additional info. + * fills *tmfd with the tuple's t_ctid, t_xmax, and, if possible, t_cmax. + * Additionally, in both success and failure cases, tmfd->traversed is set if + * an update chain was followed. See comments for struct TM_FailureData for + * additional info. */ static inline TM_Result table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, diff --git a/src/test/isolation/expected/merge-match-recheck.out b/src/test/isolation/expected/merge-match-recheck.out index 90300f1db5ab3..4250b85af2d3c 100644 --- a/src/test/isolation/expected/merge-match-recheck.out +++ b/src/test/isolation/expected/merge-match-recheck.out @@ -271,6 +271,151 @@ key|balance|status|val step c1: COMMIT; +starting permutation: update1 update6 merge_bal c2 select1 c1 +step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; +step update6: UPDATE target t SET balance = balance - 100, val = t.val || ' updated by update6' WHERE t.key = 1; +step merge_bal: + MERGE INTO target t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3'; + +step c2: COMMIT; +step merge_bal: <... completed> +step select1: SELECT * FROM target; +key|balance|status|val +---+-------+------+------------------------------------------------- + 1| 140|s1 |setup updated by update1 updated by update6 when1 +(1 row) + +step c1: COMMIT; + +starting permutation: update1_pa update6_pa merge_bal_pa c2 select1_pa c1 +step update1_pa: UPDATE target_pa t SET balance = balance + 10, val = t.val || ' updated by update1_pa' WHERE t.key = 1; +step update6_pa: UPDATE target_pa t SET balance = balance - 100, val = t.val || ' updated by update6_pa' WHERE t.key = 1; +step merge_bal_pa: + MERGE INTO target_pa t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3'; + +step c2: COMMIT; +step merge_bal_pa: <... completed> +step select1_pa: SELECT * FROM target_pa; +key|balance|status|val +---+-------+------+------------------------------------------------------- + 1| 140|s1 |setup updated by update1_pa updated by update6_pa when1 +(1 row) + +step c1: COMMIT; + +starting permutation: update1_tg update6_tg merge_bal_tg c2 select1_tg c1 +s2: NOTICE: Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg") +step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1; +s2: NOTICE: Update: (1,170,s1,"setup updated by update1_tg") -> (1,70,s1,"setup updated by update1_tg updated by update6_tg") +step update6_tg: UPDATE target_tg t SET balance = balance - 100, val = t.val || ' updated by update6_tg' WHERE t.key = 1; +step merge_bal_tg: + WITH t AS ( + MERGE INTO target_tg t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3' + RETURNING t.* + ) + SELECT * FROM t; + +step c2: COMMIT; +s1: NOTICE: Update: (1,70,s1,"setup updated by update1_tg updated by update6_tg") -> (1,140,s1,"setup updated by update1_tg updated by update6_tg when1") +step merge_bal_tg: <... completed> +key|balance|status|val +---+-------+------+------------------------------------------------------- + 1| 140|s1 |setup updated by update1_tg updated by update6_tg when1 +(1 row) + +step select1_tg: SELECT * FROM target_tg; +key|balance|status|val +---+-------+------+------------------------------------------------------- + 1| 140|s1 |setup updated by update1_tg updated by update6_tg when1 +(1 row) + +step c1: COMMIT; + +starting permutation: update7 update6 merge_bal c2 select1 c1 +step update7: UPDATE target t SET balance = 350, val = t.val || ' updated by update7' WHERE t.key = 1; +step update6: UPDATE target t SET balance = balance - 100, val = t.val || ' updated by update6' WHERE t.key = 1; +step merge_bal: + MERGE INTO target t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3'; + +step c2: COMMIT; +step merge_bal: <... completed> +step select1: SELECT * FROM target; +key|balance|status|val +---+-------+------+------------------------------------------------- + 1| 2000|s1 |setup updated by update7 updated by update6 when3 +(1 row) + +step c1: COMMIT; + +starting permutation: update1_pa_move merge_bal_pa c2 c1 +step update1_pa_move: UPDATE target_pa t SET balance = 210, val = t.val || ' updated by update1_pa_move' WHERE t.key = 1; +step merge_bal_pa: + MERGE INTO target_pa t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3'; + +step c2: COMMIT; +step merge_bal_pa: <... completed> +ERROR: tuple to be locked was already moved to another partition due to concurrent update +step c1: COMMIT; + +starting permutation: update1_pa update1_pa_move merge_bal_pa c2 c1 +step update1_pa: UPDATE target_pa t SET balance = balance + 10, val = t.val || ' updated by update1_pa' WHERE t.key = 1; +step update1_pa_move: UPDATE target_pa t SET balance = 210, val = t.val || ' updated by update1_pa_move' WHERE t.key = 1; +step merge_bal_pa: + MERGE INTO target_pa t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3'; + +step c2: COMMIT; +step merge_bal_pa: <... completed> +ERROR: tuple to be locked was already moved to another partition due to concurrent update +step c1: COMMIT; + starting permutation: update1 merge_delete c2 select1 c1 step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; step merge_delete: diff --git a/src/test/isolation/specs/merge-match-recheck.spec b/src/test/isolation/specs/merge-match-recheck.spec index 15226e40c9efc..6e7a776d17e5a 100644 --- a/src/test/isolation/specs/merge-match-recheck.spec +++ b/src/test/isolation/specs/merge-match-recheck.spec @@ -146,6 +146,8 @@ setup BEGIN ISOLATION LEVEL READ COMMITTED; } step "update1" { UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; } +step "update1_pa" { UPDATE target_pa t SET balance = balance + 10, val = t.val || ' updated by update1_pa' WHERE t.key = 1; } +step "update1_pa_move" { UPDATE target_pa t SET balance = 210, val = t.val || ' updated by update1_pa_move' WHERE t.key = 1; } step "update1_tg" { UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1; } step "update2" { UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1; } step "update2_tg" { UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1; } @@ -153,6 +155,10 @@ step "update3" { UPDATE target t SET status = 's3', val = t.val || ' updated by step "update3_tg" { UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1; } step "update5" { UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1; } step "update5_tg" { UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1; } +step "update6" { UPDATE target t SET balance = balance - 100, val = t.val || ' updated by update6' WHERE t.key = 1; } +step "update6_pa" { UPDATE target_pa t SET balance = balance - 100, val = t.val || ' updated by update6_pa' WHERE t.key = 1; } +step "update6_tg" { UPDATE target_tg t SET balance = balance - 100, val = t.val || ' updated by update6_tg' WHERE t.key = 1; } +step "update7" { UPDATE target t SET balance = 350, val = t.val || ' updated by update7' WHERE t.key = 1; } step "update_bal1" { UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1; } step "update_bal1_pa" { UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1; } step "update_bal1_tg" { UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1; } @@ -179,6 +185,18 @@ permutation "update_bal1" "merge_bal" "c2" "select1" "c1" permutation "update_bal1_pa" "merge_bal_pa" "c2" "select1_pa" "c1" permutation "update_bal1_tg" "merge_bal_tg" "c2" "select1_tg" "c1" +# merge_bal sees row concurrently updated twice and rechecks WHEN conditions, different check passes, so final balance = 140 +permutation "update1" "update6" "merge_bal" "c2" "select1" "c1" +permutation "update1_pa" "update6_pa" "merge_bal_pa" "c2" "select1_pa" "c1" +permutation "update1_tg" "update6_tg" "merge_bal_tg" "c2" "select1_tg" "c1" + +# merge_bal sees row concurrently updated twice, first update would cause all checks to fail, second update causes different check to pass, so final balance = 2000 +permutation "update7" "update6" "merge_bal" "c2" "select1" "c1" + +# merge_bal sees concurrently updated row moved to new partition, so fails +permutation "update1_pa_move" "merge_bal_pa" "c2" "c1" +permutation "update1_pa" "update1_pa_move" "merge_bal_pa" "c2" "c1" + # merge_delete sees concurrently updated row and rechecks WHEN conditions, but recheck passes and row is deleted permutation "update1" "merge_delete" "c2" "select1" "c1" permutation "update1_tg" "merge_delete_tg" "c2" "select1_tg" "c1" From e3d5ddb7ca91e5982e9d4cff9eef210d97e4f47e Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Fri, 5 Sep 2025 09:33:36 -0400 Subject: [PATCH 02/96] Add assert and log message to visibilitymap_set Add an assert to visibilitymap_set() that the provided heap buffer is exclusively locked, which is expected. Also, enhance the debug logging message to specify which VM flags were set. Based on a related suggestion by Kirill Reshke on an in-progress patchset. Author: Melanie Plageman Reviewed-by: Kirill Reshke Reviewed-by: Andres Freund Discussion: https://postgr.es/m/CALdSSPhAU56g1gGVT0%2BwG8RrSWE6qW8TOfNJS1HNAWX6wPgbFA%40mail.gmail.com --- src/backend/access/heap/visibilitymap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 953ad4a484399..7306c16f05cd3 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -255,7 +255,8 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, uint8 status; #ifdef TRACE_VISIBILITYMAP - elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk); + elog(DEBUG1, "vm_set flags 0x%02X for %s %d", + flags, RelationGetRelationName(rel), heapBlk); #endif Assert(InRecovery || XLogRecPtrIsInvalid(recptr)); @@ -269,6 +270,8 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, if (BufferIsValid(heapBuf) && BufferGetBlockNumber(heapBuf) != heapBlk) elog(ERROR, "wrong heap buffer passed to visibilitymap_set"); + Assert(!BufferIsValid(heapBuf) || BufferIsExclusiveLocked(heapBuf)); + /* Check that we have the right VM page pinned */ if (!BufferIsValid(vmBuf) || BufferGetBlockNumber(vmBuf) != mapBlock) elog(ERROR, "wrong VM buffer passed to visibilitymap_set"); From 50e4c6ace5e69fbe69868c270a1c76acd4cb12ec Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 5 Sep 2025 12:25:59 -0400 Subject: [PATCH 03/96] bufmgr: Use consistent naming of the clock-sweep algorithm Minor edits to comments only. Author: Greg Burd Reviewed-by: Tomas Vondra Reviewed-by: Andres Freund Discussion: https://postgr.es/m/70C6A5B5-2A20-4D0B-BC73-EB09DD62D61C@getmailspring.com --- src/backend/storage/buffer/README | 4 ++-- src/backend/storage/buffer/bufmgr.c | 8 ++++---- src/backend/storage/buffer/freelist.c | 10 +++++----- src/backend/storage/buffer/localbuf.c | 2 +- src/include/storage/buf_internals.h | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README index a182fcd660ccb..4b13da5d7add8 100644 --- a/src/backend/storage/buffer/README +++ b/src/backend/storage/buffer/README @@ -211,9 +211,9 @@ Buffer Ring Replacement Strategy When running a query that needs to access a large number of pages just once, such as VACUUM or a large sequential scan, a different strategy is used. A page that has been touched only by such a scan is unlikely to be needed -again soon, so instead of running the normal clock sweep algorithm and +again soon, so instead of running the normal clock-sweep algorithm and blowing out the entire buffer cache, a small ring of buffers is allocated -using the normal clock sweep algorithm and those buffers are reused for the +using the normal clock-sweep algorithm and those buffers are reused for the whole scan. This also implies that much of the write traffic caused by such a statement will be done by the backend itself and not pushed off onto other processes. diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 350cc0402aa8f..9fc906a4a4082 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3608,7 +3608,7 @@ BufferSync(int flags) * This is called periodically by the background writer process. * * Returns true if it's appropriate for the bgwriter process to go into - * low-power hibernation mode. (This happens if the strategy clock sweep + * low-power hibernation mode. (This happens if the strategy clock-sweep * has been "lapped" and no buffer allocations have occurred recently, * or if the bgwriter has been effectively disabled by setting * bgwriter_lru_maxpages to 0.) @@ -3658,7 +3658,7 @@ BgBufferSync(WritebackContext *wb_context) uint32 new_recent_alloc; /* - * Find out where the freelist clock sweep currently is, and how many + * Find out where the freelist clock-sweep currently is, and how many * buffer allocations have happened since our last call. */ strategy_buf_id = StrategySyncStart(&strategy_passes, &recent_alloc); @@ -3679,8 +3679,8 @@ BgBufferSync(WritebackContext *wb_context) /* * Compute strategy_delta = how many buffers have been scanned by the - * clock sweep since last time. If first time through, assume none. Then - * see if we are still ahead of the clock sweep, and if so, how many + * clock-sweep since last time. If first time through, assume none. Then + * see if we are still ahead of the clock-sweep, and if so, how many * buffers we could scan before we'd catch up with it and "lap" it. Note: * weird-looking coding of xxx_passes comparisons are to avoid bogus * behavior when the passes counts wrap around. diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c index 01909be027258..cd94a7d8a7b39 100644 --- a/src/backend/storage/buffer/freelist.c +++ b/src/backend/storage/buffer/freelist.c @@ -33,7 +33,7 @@ typedef struct slock_t buffer_strategy_lock; /* - * Clock sweep hand: index of next buffer to consider grabbing. Note that + * clock-sweep hand: index of next buffer to consider grabbing. Note that * this isn't a concrete buffer - we only ever increase the value. So, to * get an actual buffer, it needs to be used modulo NBuffers. */ @@ -51,7 +51,7 @@ typedef struct * Statistics. These counters should be wide enough that they can't * overflow during a single bgwriter cycle. */ - uint32 completePasses; /* Complete cycles of the clock sweep */ + uint32 completePasses; /* Complete cycles of the clock-sweep */ pg_atomic_uint32 numBufferAllocs; /* Buffers allocated since last reset */ /* @@ -311,7 +311,7 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state, bool *from_r } } - /* Nothing on the freelist, so run the "clock sweep" algorithm */ + /* Nothing on the freelist, so run the "clock-sweep" algorithm */ trycounter = NBuffers; for (;;) { @@ -511,7 +511,7 @@ StrategyInitialize(bool init) StrategyControl->firstFreeBuffer = 0; StrategyControl->lastFreeBuffer = NBuffers - 1; - /* Initialize the clock sweep pointer */ + /* Initialize the clock-sweep pointer */ pg_atomic_init_u32(&StrategyControl->nextVictimBuffer, 0); /* Clear statistics */ @@ -759,7 +759,7 @@ GetBufferFromRing(BufferAccessStrategy strategy, uint32 *buf_state) * * If usage_count is 0 or 1 then the buffer is fair game (we expect 1, * since our own previous usage of the ring element would have left it - * there, but it might've been decremented by clock sweep since then). A + * there, but it might've been decremented by clock-sweep since then). A * higher usage_count indicates someone else has touched the buffer, so we * shouldn't re-use it. */ diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c index 3c0d20f4659d2..04fef13409b02 100644 --- a/src/backend/storage/buffer/localbuf.c +++ b/src/backend/storage/buffer/localbuf.c @@ -229,7 +229,7 @@ GetLocalVictimBuffer(void) ResourceOwnerEnlarge(CurrentResourceOwner); /* - * Need to get a new buffer. We use a clock sweep algorithm (essentially + * Need to get a new buffer. We use a clock-sweep algorithm (essentially * the same as what freelist.c does now...) */ trycounter = NLocBuffer; diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index 52a71b138f736..3a210c710f633 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -80,8 +80,8 @@ StaticAssertDecl(BUF_REFCOUNT_BITS + BUF_USAGECOUNT_BITS + BUF_FLAG_BITS == 32, * The maximum allowed value of usage_count represents a tradeoff between * accuracy and speed of the clock-sweep buffer management algorithm. A * large value (comparable to NBuffers) would approximate LRU semantics. - * But it can take as many as BM_MAX_USAGE_COUNT+1 complete cycles of - * clock sweeps to find a free buffer, so in practice we don't want the + * But it can take as many as BM_MAX_USAGE_COUNT+1 complete cycles of the + * clock-sweep hand to find a free buffer, so in practice we don't want the * value to be very large. */ #define BM_MAX_USAGE_COUNT 5 From 2c789405275928ce0d2ceb7c4add91d27df92502 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 5 Sep 2025 12:25:59 -0400 Subject: [PATCH 04/96] bufmgr: Remove freelist, always use clock-sweep This set of changes removes the list of available buffers and instead simply uses the clock-sweep algorithm to find and return an available buffer. This also removes the have_free_buffer() function and simply caps the pg_autoprewarm process to at most NBuffers. While on the surface this appears to be removing an optimization it is in fact eliminating code that induces overhead in the form of synchronization that is problematic for multi-core systems. The main reason for removing the freelist, however, is not the moderate improvement in scalability, but that having the freelist would require dedicated complexity in several upcoming patches. As we have not been able to find a case benefiting from the freelist... Author: Greg Burd Reviewed-by: Tomas Vondra Reviewed-by: Andres Freund Discussion: https://postgr.es/m/70C6A5B5-2A20-4D0B-BC73-EB09DD62D61C@getmailspring.com --- contrib/pg_prewarm/autoprewarm.c | 30 +++---- src/backend/storage/buffer/README | 40 +++------ src/backend/storage/buffer/buf_init.c | 9 -- src/backend/storage/buffer/bufmgr.c | 29 +------ src/backend/storage/buffer/freelist.c | 119 +------------------------- src/include/storage/buf_internals.h | 13 +-- 6 files changed, 31 insertions(+), 209 deletions(-) diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c index 880e897796a1e..8b68dafc2611c 100644 --- a/contrib/pg_prewarm/autoprewarm.c +++ b/contrib/pg_prewarm/autoprewarm.c @@ -370,6 +370,15 @@ apw_load_buffers(void) apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx = 0; apw_state->prewarmed_blocks = 0; + /* Don't prewarm more than we can fit. */ + if (num_elements > NBuffers) + { + num_elements = NBuffers; + ereport(LOG, + (errmsg("autoprewarm capping prewarmed blocks to %d (shared_buffers size)", + NBuffers))); + } + /* Get the info position of the first block of the next database. */ while (apw_state->prewarm_start_idx < num_elements) { @@ -410,10 +419,6 @@ apw_load_buffers(void) apw_state->database = current_db; Assert(apw_state->prewarm_start_idx < apw_state->prewarm_stop_idx); - /* If we've run out of free buffers, don't launch another worker. */ - if (!have_free_buffer()) - break; - /* * Likewise, don't launch if we've already been told to shut down. * (The launch would fail anyway, but we might as well skip it.) @@ -462,12 +467,6 @@ apw_read_stream_next_block(ReadStream *stream, { BlockInfoRecord blk = p->block_info[p->pos]; - if (!have_free_buffer()) - { - p->pos = apw_state->prewarm_stop_idx; - return InvalidBlockNumber; - } - if (blk.tablespace != p->tablespace) return InvalidBlockNumber; @@ -523,10 +522,10 @@ autoprewarm_database_main(Datum main_arg) blk = block_info[i]; /* - * Loop until we run out of blocks to prewarm or until we run out of free + * Loop until we run out of blocks to prewarm or until we run out of * buffers. */ - while (i < apw_state->prewarm_stop_idx && have_free_buffer()) + while (i < apw_state->prewarm_stop_idx) { Oid tablespace = blk.tablespace; RelFileNumber filenumber = blk.filenumber; @@ -568,14 +567,13 @@ autoprewarm_database_main(Datum main_arg) /* * We have a relation; now let's loop until we find a valid fork of - * the relation or we run out of free buffers. Once we've read from - * all valid forks or run out of options, we'll close the relation and + * the relation or we run out of buffers. Once we've read from all + * valid forks or run out of options, we'll close the relation and * move on. */ while (i < apw_state->prewarm_stop_idx && blk.tablespace == tablespace && - blk.filenumber == filenumber && - have_free_buffer()) + blk.filenumber == filenumber) { ForkNumber forknum = blk.forknum; BlockNumber nblocks; diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README index 4b13da5d7add8..119f31b5d6584 100644 --- a/src/backend/storage/buffer/README +++ b/src/backend/storage/buffer/README @@ -128,11 +128,11 @@ independently. If it is necessary to lock more than one partition at a time, they must be locked in partition-number order to avoid risk of deadlock. * A separate system-wide spinlock, buffer_strategy_lock, provides mutual -exclusion for operations that access the buffer free list or select -buffers for replacement. A spinlock is used here rather than a lightweight -lock for efficiency; no other locks of any sort should be acquired while -buffer_strategy_lock is held. This is essential to allow buffer replacement -to happen in multiple backends with reasonable concurrency. +exclusion for operations that select buffers for replacement. A spinlock is +used here rather than a lightweight lock for efficiency; no other locks of any +sort should be acquired while buffer_strategy_lock is held. This is essential +to allow buffer replacement to happen in multiple backends with reasonable +concurrency. * Each buffer header contains a spinlock that must be taken when examining or changing fields of that buffer header. This allows operations such as @@ -158,18 +158,8 @@ unset by sleeping on the buffer's condition variable. Normal Buffer Replacement Strategy ---------------------------------- -There is a "free list" of buffers that are prime candidates for replacement. -In particular, buffers that are completely free (contain no valid page) are -always in this list. We could also throw buffers into this list if we -consider their pages unlikely to be needed soon; however, the current -algorithm never does that. The list is singly-linked using fields in the -buffer headers; we maintain head and tail pointers in global variables. -(Note: although the list links are in the buffer headers, they are -considered to be protected by the buffer_strategy_lock, not the buffer-header -spinlocks.) To choose a victim buffer to recycle when there are no free -buffers available, we use a simple clock-sweep algorithm, which avoids the -need to take system-wide locks during common operations. It works like -this: +To choose a victim buffer to recycle we use a simple clock-sweep algorithm. It +works like this: Each buffer header contains a usage counter, which is incremented (up to a small limit value) whenever the buffer is pinned. (This requires only the @@ -184,20 +174,14 @@ The algorithm for a process that needs to obtain a victim buffer is: 1. Obtain buffer_strategy_lock. -2. If buffer free list is nonempty, remove its head buffer. Release -buffer_strategy_lock. If the buffer is pinned or has a nonzero usage count, -it cannot be used; ignore it go back to step 1. Otherwise, pin the buffer, -and return it. +2. Select the buffer pointed to by nextVictimBuffer, and circularly advance +nextVictimBuffer for next time. Release buffer_strategy_lock. -3. Otherwise, the buffer free list is empty. Select the buffer pointed to by -nextVictimBuffer, and circularly advance nextVictimBuffer for next time. -Release buffer_strategy_lock. - -4. If the selected buffer is pinned or has a nonzero usage count, it cannot +3. If the selected buffer is pinned or has a nonzero usage count, it cannot be used. Decrement its usage count (if nonzero), reacquire buffer_strategy_lock, and return to step 3 to examine the next buffer. -5. Pin the selected buffer, and return. +4. Pin the selected buffer, and return. (Note that if the selected buffer is dirty, we will have to write it out before we can recycle it; if someone else pins the buffer meanwhile we will @@ -234,7 +218,7 @@ the ring strategy effectively degrades to the normal strategy. VACUUM uses a ring like sequential scans, however, the size of this ring is controlled by the vacuum_buffer_usage_limit GUC. Dirty pages are not removed -from the ring. Instead, WAL is flushed if needed to allow reuse of the +from the ring. Instead, the WAL is flushed if needed to allow reuse of the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's buffers were sent to the freelist, which was effectively a buffer ring of 1 buffer, resulting in excessive WAL flushing. diff --git a/src/backend/storage/buffer/buf_init.c b/src/backend/storage/buffer/buf_init.c index ed1dc488a42b4..6fd3a6bbac5ea 100644 --- a/src/backend/storage/buffer/buf_init.c +++ b/src/backend/storage/buffer/buf_init.c @@ -128,20 +128,11 @@ BufferManagerShmemInit(void) pgaio_wref_clear(&buf->io_wref); - /* - * Initially link all the buffers together as unused. Subsequent - * management of this list is done by freelist.c. - */ - buf->freeNext = i + 1; - LWLockInitialize(BufferDescriptorGetContentLock(buf), LWTRANCHE_BUFFER_CONTENT); ConditionVariableInit(BufferDescriptorGetIOCV(buf)); } - - /* Correct last entry of linked list */ - GetBufferDescriptor(NBuffers - 1)->freeNext = FREENEXT_END_OF_LIST; } /* Init other shared buffer-management stuff */ diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 9fc906a4a4082..fe470de63f20c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -2094,12 +2094,6 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, */ UnpinBuffer(victim_buf_hdr); - /* - * The victim buffer we acquired previously is clean and unused, let - * it be found again quickly - */ - StrategyFreeBuffer(victim_buf_hdr); - /* remaining code should match code at top of routine */ existing_buf_hdr = GetBufferDescriptor(existing_buf_id); @@ -2158,8 +2152,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, } /* - * InvalidateBuffer -- mark a shared buffer invalid and return it to the - * freelist. + * InvalidateBuffer -- mark a shared buffer invalid. * * The buffer header spinlock must be held at entry. We drop it before * returning. (This is sane because the caller must have locked the @@ -2257,11 +2250,6 @@ InvalidateBuffer(BufferDesc *buf) * Done with mapping lock. */ LWLockRelease(oldPartitionLock); - - /* - * Insert the buffer at the head of the list of free buffers. - */ - StrategyFreeBuffer(buf); } /* @@ -2679,11 +2667,6 @@ ExtendBufferedRelShared(BufferManagerRelation bmr, { BufferDesc *buf_hdr = GetBufferDescriptor(buffers[i] - 1); - /* - * The victim buffer we acquired previously is clean and unused, - * let it be found again quickly - */ - StrategyFreeBuffer(buf_hdr); UnpinBuffer(buf_hdr); } @@ -2756,12 +2739,6 @@ ExtendBufferedRelShared(BufferManagerRelation bmr, valid = PinBuffer(existing_hdr, strategy); LWLockRelease(partition_lock); - - /* - * The victim buffer we acquired previously is clean and unused, - * let it be found again quickly - */ - StrategyFreeBuffer(victim_buf_hdr); UnpinBuffer(victim_buf_hdr); buffers[i] = BufferDescriptorGetBuffer(existing_hdr); @@ -3658,8 +3635,8 @@ BgBufferSync(WritebackContext *wb_context) uint32 new_recent_alloc; /* - * Find out where the freelist clock-sweep currently is, and how many - * buffer allocations have happened since our last call. + * Find out where the clock-sweep currently is, and how many buffer + * allocations have happened since our last call. */ strategy_buf_id = StrategySyncStart(&strategy_passes, &recent_alloc); diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c index cd94a7d8a7b39..7d59a92bd1a88 100644 --- a/src/backend/storage/buffer/freelist.c +++ b/src/backend/storage/buffer/freelist.c @@ -39,14 +39,6 @@ typedef struct */ pg_atomic_uint32 nextVictimBuffer; - int firstFreeBuffer; /* Head of list of unused buffers */ - int lastFreeBuffer; /* Tail of list of unused buffers */ - - /* - * NOTE: lastFreeBuffer is undefined when firstFreeBuffer is -1 (that is, - * when the list is empty) - */ - /* * Statistics. These counters should be wide enough that they can't * overflow during a single bgwriter cycle. @@ -163,23 +155,6 @@ ClockSweepTick(void) return victim; } -/* - * have_free_buffer -- a lockless check to see if there is a free buffer in - * buffer pool. - * - * If the result is true that will become stale once free buffers are moved out - * by other operations, so the caller who strictly want to use a free buffer - * should not call this. - */ -bool -have_free_buffer(void) -{ - if (StrategyControl->firstFreeBuffer >= 0) - return true; - else - return false; -} - /* * StrategyGetBuffer * @@ -249,69 +224,7 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state, bool *from_r */ pg_atomic_fetch_add_u32(&StrategyControl->numBufferAllocs, 1); - /* - * First check, without acquiring the lock, whether there's buffers in the - * freelist. Since we otherwise don't require the spinlock in every - * StrategyGetBuffer() invocation, it'd be sad to acquire it here - - * uselessly in most cases. That obviously leaves a race where a buffer is - * put on the freelist but we don't see the store yet - but that's pretty - * harmless, it'll just get used during the next buffer acquisition. - * - * If there's buffers on the freelist, acquire the spinlock to pop one - * buffer of the freelist. Then check whether that buffer is usable and - * repeat if not. - * - * Note that the freeNext fields are considered to be protected by the - * buffer_strategy_lock not the individual buffer spinlocks, so it's OK to - * manipulate them without holding the spinlock. - */ - if (StrategyControl->firstFreeBuffer >= 0) - { - while (true) - { - /* Acquire the spinlock to remove element from the freelist */ - SpinLockAcquire(&StrategyControl->buffer_strategy_lock); - - if (StrategyControl->firstFreeBuffer < 0) - { - SpinLockRelease(&StrategyControl->buffer_strategy_lock); - break; - } - - buf = GetBufferDescriptor(StrategyControl->firstFreeBuffer); - Assert(buf->freeNext != FREENEXT_NOT_IN_LIST); - - /* Unconditionally remove buffer from freelist */ - StrategyControl->firstFreeBuffer = buf->freeNext; - buf->freeNext = FREENEXT_NOT_IN_LIST; - - /* - * Release the lock so someone else can access the freelist while - * we check out this buffer. - */ - SpinLockRelease(&StrategyControl->buffer_strategy_lock); - - /* - * If the buffer is pinned or has a nonzero usage_count, we cannot - * use it; discard it and retry. (This can only happen if VACUUM - * put a valid buffer in the freelist and then someone else used - * it before we got to it. It's probably impossible altogether as - * of 8.3, but we'd better check anyway.) - */ - local_buf_state = LockBufHdr(buf); - if (BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 - && BUF_STATE_GET_USAGECOUNT(local_buf_state) == 0) - { - if (strategy != NULL) - AddBufferToRing(strategy, buf); - *buf_state = local_buf_state; - return buf; - } - UnlockBufHdr(buf, local_buf_state); - } - } - - /* Nothing on the freelist, so run the "clock-sweep" algorithm */ + /* Use the "clock sweep" algorithm to find a free buffer */ trycounter = NBuffers; for (;;) { @@ -356,29 +269,6 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state, bool *from_r } } -/* - * StrategyFreeBuffer: put a buffer on the freelist - */ -void -StrategyFreeBuffer(BufferDesc *buf) -{ - SpinLockAcquire(&StrategyControl->buffer_strategy_lock); - - /* - * It is possible that we are told to put something in the freelist that - * is already in it; don't screw up the list if so. - */ - if (buf->freeNext == FREENEXT_NOT_IN_LIST) - { - buf->freeNext = StrategyControl->firstFreeBuffer; - if (buf->freeNext < 0) - StrategyControl->lastFreeBuffer = buf->buf_id; - StrategyControl->firstFreeBuffer = buf->buf_id; - } - - SpinLockRelease(&StrategyControl->buffer_strategy_lock); -} - /* * StrategySyncStart -- tell BgBufferSync where to start syncing * @@ -504,13 +394,6 @@ StrategyInitialize(bool init) SpinLockInit(&StrategyControl->buffer_strategy_lock); - /* - * Grab the whole linked list of free buffers for our strategy. We - * assume it was previously set up by BufferManagerShmemInit(). - */ - StrategyControl->firstFreeBuffer = 0; - StrategyControl->lastFreeBuffer = NBuffers - 1; - /* Initialize the clock-sweep pointer */ pg_atomic_init_u32(&StrategyControl->nextVictimBuffer, 0); diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index 3a210c710f633..dfd614f7ca449 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -217,8 +217,7 @@ BufMappingPartitionLockByIndex(uint32 index) * single atomic variable. This layout allow us to do some operations in a * single atomic operation, without actually acquiring and releasing spinlock; * for instance, increase or decrease refcount. buf_id field never changes - * after initialization, so does not need locking. freeNext is protected by - * the buffer_strategy_lock not buffer header lock. The LWLock can take care + * after initialization, so does not need locking. The LWLock can take care * of itself. The buffer header lock is *not* used to control access to the * data in the buffer! * @@ -264,7 +263,6 @@ typedef struct BufferDesc pg_atomic_uint32 state; int wait_backend_pgprocno; /* backend of pin-count waiter */ - int freeNext; /* link in freelist chain */ PgAioWaitRef io_wref; /* set iff AIO is in progress */ LWLock content_lock; /* to lock access to buffer contents */ @@ -360,13 +358,6 @@ BufferDescriptorGetContentLock(const BufferDesc *bdesc) return (LWLock *) (&bdesc->content_lock); } -/* - * The freeNext field is either the index of the next freelist entry, - * or one of these special values: - */ -#define FREENEXT_END_OF_LIST (-1) -#define FREENEXT_NOT_IN_LIST (-2) - /* * Functions for acquiring/releasing a shared buffer header's spinlock. Do * not apply these to local buffers! @@ -444,7 +435,6 @@ extern void TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag extern IOContext IOContextForStrategy(BufferAccessStrategy strategy); extern BufferDesc *StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state, bool *from_ring); -extern void StrategyFreeBuffer(BufferDesc *buf); extern bool StrategyRejectBuffer(BufferAccessStrategy strategy, BufferDesc *buf, bool from_ring); @@ -453,7 +443,6 @@ extern void StrategyNotifyBgWriter(int bgwprocno); extern Size StrategyShmemSize(void); extern void StrategyInitialize(bool init); -extern bool have_free_buffer(void); /* buf_table.c */ extern Size BufTableShmemSize(int size); From 06473f5a344df8c9594ead90a609b86f6724cff8 Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Sat, 6 Sep 2025 07:49:51 +0900 Subject: [PATCH 05/96] Allow to log raw parse tree. This commit allows to log the raw parse tree in the same way we currently log the parse tree, rewritten tree, and plan tree. To avoid unnecessary log noise for users not interested in this detail, a new GUC option, "debug_print_raw_parse", has been added. When starting the PostgreSQL process with "-d N", and N is 3 or higher, debug_print_raw_parse is enabled automatically, alongside debug_print_parse. Author: Chao Li Reviewed-by: Tender Wang Reviewed-by: Tatsuo Ishii Reviewed-by: John Naylor Discussion: https://postgr.es/m/CAEoWx2mcO0Gpo4vd8kPMAFWeJLSp0MeUUnaLdE1x0tSVd-VzUw%40mail.gmail.com --- doc/src/sgml/config.sgml | 12 +++++++++--- doc/src/sgml/rules.sgml | 1 + src/backend/tcop/postgres.c | 7 +++++++ src/backend/utils/misc/guc_parameters.dat | 6 ++++++ src/backend/utils/misc/guc_tables.c | 1 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/utils/guc.h | 1 + 7 files changed, 26 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 0a4b3e55ba5ed..2a3685f474a96 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7383,6 +7383,11 @@ local0.* /var/log/postgresql + debug_print_raw_parse (boolean) + + debug_print_raw_parse configuration parameter + + debug_print_parse (boolean) debug_print_parse configuration parameter @@ -7401,8 +7406,8 @@ local0.* /var/log/postgresql These parameters enable various debugging output to be emitted. - When set, they print the resulting parse tree, the query rewriter - output, or the execution plan for each executed query. + When set, they print the resulting raw parse tree, the parse tree, the query + rewriter output, or the execution plan for each executed query. These messages are emitted at LOG message level, so by default they will appear in the server log but will not be sent to the client. You can change that by adjusting @@ -7422,7 +7427,8 @@ local0.* /var/log/postgresql When set, debug_pretty_print indents the messages - produced by debug_print_parse, + produced by debug_print_raw_parse, + debug_print_parse, debug_print_rewritten, or debug_print_plan. This results in more readable but much longer output than the compact format used when diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml index 8467d961fd0a0..282dcd722d495 100644 --- a/doc/src/sgml/rules.sgml +++ b/doc/src/sgml/rules.sgml @@ -60,6 +60,7 @@ SQL statement where the single parts that it is built from are stored separately. These query trees can be shown in the server log if you set the configuration parameters + debug_print_raw_parse, debug_print_parse, debug_print_rewritten, or debug_print_plan. The rule actions are also diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0cecd4649020f..d356830f756be 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -649,6 +649,10 @@ pg_parse_query(const char *query_string) TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string); + if (Debug_print_raw_parse) + elog_node_display(LOG, "raw parse tree", raw_parsetree_list, + Debug_pretty_print); + return raw_parsetree_list; } @@ -3697,7 +3701,10 @@ set_debug_options(int debug_flag, GucContext context, GucSource source) if (debug_flag >= 2) SetConfigOption("log_statement", "all", context, source); if (debug_flag >= 3) + { + SetConfigOption("debug_print_raw_parse", "true", context, source); SetConfigOption("debug_print_parse", "true", context, source); + } if (debug_flag >= 4) SetConfigOption("debug_print_plan", "true", context, source); if (debug_flag >= 5) diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index a157cec3c4d00..0da01627cfec1 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -414,6 +414,12 @@ ifdef => 'DEBUG_NODE_TESTS_ENABLED', }, +{ name => 'debug_print_raw_parse', type => 'bool', context => 'PGC_USERSET', group => 'LOGGING_WHAT', + short_desc => 'Logs each query\'s raw parse tree.', + variable => 'Debug_print_raw_parse', + boot_val => 'false', +}, + { name => 'debug_print_parse', type => 'bool', context => 'PGC_USERSET', group => 'LOGGING_WHAT', short_desc => 'Logs each query\'s parse tree.', variable => 'Debug_print_parse', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 787933a9e5acd..00c8376cf4ded 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -507,6 +507,7 @@ bool AllowAlterSystem = true; bool log_duration = false; bool Debug_print_plan = false; bool Debug_print_parse = false; +bool Debug_print_raw_parse = false; bool Debug_print_rewritten = false; bool Debug_pretty_print = true; diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a9d8293474af5..26c0869356485 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -581,6 +581,7 @@ # - What to Log - +#debug_print_raw_parse = off #debug_print_parse = off #debug_print_rewritten = off #debug_print_plan = off diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 72981053e610f..756e80a2c2fcc 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -247,6 +247,7 @@ typedef enum /* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */ extern PGDLLIMPORT bool Debug_print_plan; extern PGDLLIMPORT bool Debug_print_parse; +extern PGDLLIMPORT bool Debug_print_raw_parse; extern PGDLLIMPORT bool Debug_print_rewritten; extern PGDLLIMPORT bool Debug_pretty_print; From 43eb2c541941479714c11de9cfb7c67b54f1810d Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 8 Sep 2025 10:07:14 +0900 Subject: [PATCH 06/96] Update parser README to include parse_jsontable.c The README was missing parse_jsontable.c which handles JSON_TABLE. Oversight in de3600452b61. Author: Karthik S Discussion: https://postgr.es/m/CAK4gQD9gdcj+vq_FZGp=Rv-W+41v8_C7cmCUmDeu=cfrOdfXEw@mail.gmail.com Backpatch-through: 17 --- src/backend/parser/README | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/parser/README b/src/backend/parser/README index e0c986a41efea..e26eb437a9f35 100644 --- a/src/backend/parser/README +++ b/src/backend/parser/README @@ -20,6 +20,7 @@ parse_cte.c handle Common Table Expressions (WITH clauses) parse_expr.c handle expressions like col, col + 3, x = 3 or x = 4 parse_enr.c handle ephemeral named rels (trigger transition tables, ...) parse_func.c handle functions, table.column and column identifiers +parse_jsontable.c handle JSON_TABLE parse_merge.c handle MERGE parse_node.c create nodes for various structures parse_oper.c handle operators in expressions From 1f7e9ba3ac4eff13041abcc4c9c517ad835fa449 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 8 Sep 2025 06:10:15 +0000 Subject: [PATCH 07/96] Post-commit review fixes for 228c370868. This commit fixes three issues: 1) When a disabled subscription is created with retain_dead_tuples set to true, the launcher is not woken up immediately, which may lead to delays in creating the conflict detection slot. Creating the conflict detection slot is essential even when the subscription is not enabled. This ensures that dead tuples are retained, which is necessary for accurately identifying the type of conflict during replication. 2) Conflict-related data was unnecessarily retained when the subscription does not have a table. 3) Conflict-relevant data could be prematurely removed before applying prepared transactions on the publisher that are in the commit critical section. This issue occurred because the backend executing COMMIT PREPARED was not accounted for during the computation of oldestXid in the commit phase on the publisher. As a result, the subscriber could advance the conflict slot's xmin without waiting for such COMMIT PREPARED transactions to complete. We fixed this issue by identifying prepared transactions that are in the commit critical section during computation of oldestXid in commit phase. Author: Zhijie Hou Reviewed-by: shveta malik Reviewed-by: Dilip Kumar Reviewed-by: Nisha Moond Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/OS9PR01MB16913DACB64E5721872AA5C02943BA@OS9PR01MB16913.jpnprd01.prod.outlook.com Discussion: https://postgr.es/m/OS9PR01MB16913F67856B0DA2A909788129400A@OS9PR01MB16913.jpnprd01.prod.outlook.com --- src/backend/access/transam/twophase.c | 55 +++++++++++++++++++++ src/backend/commands/subscriptioncmds.c | 12 ++++- src/backend/replication/logical/tablesync.c | 26 ++++++++++ src/backend/replication/logical/worker.c | 25 ++++++++-- src/backend/replication/walsender.c | 12 +++++ src/include/access/twophase.h | 2 + src/include/replication/worker_internal.h | 1 + src/test/subscription/t/035_conflicts.pl | 29 +++++++++++ 8 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 7918176fc588e..3e20f4487872e 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -2809,3 +2809,58 @@ LookupGXactBySubid(Oid subid) return found; } + +/* + * TwoPhaseGetXidByLockingProc + * Return the oldest transaction ID from prepared transactions that are + * currently in the commit critical section. + * + * This function only considers transactions in the currently connected + * database. If no matching transactions are found, it returns + * InvalidTransactionId. + */ +TransactionId +TwoPhaseGetOldestXidInCommit(void) +{ + TransactionId oldestRunningXid = InvalidTransactionId; + + LWLockAcquire(TwoPhaseStateLock, LW_SHARED); + + for (int i = 0; i < TwoPhaseState->numPrepXacts; i++) + { + GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; + PGPROC *commitproc; + TransactionId xid; + + if (!gxact->valid) + continue; + + if (gxact->locking_backend == INVALID_PROC_NUMBER) + continue; + + /* + * Get the backend that is handling the transaction. It's safe to + * access this backend while holding TwoPhaseStateLock, as the backend + * can only be destroyed after either removing or unlocking the + * current global transaction, both of which require an exclusive + * TwoPhaseStateLock. + */ + commitproc = GetPGProcByNumber(gxact->locking_backend); + + if (MyDatabaseId != commitproc->databaseId) + continue; + + if ((commitproc->delayChkptFlags & DELAY_CHKPT_IN_COMMIT) == 0) + continue; + + xid = XidFromFullTransactionId(gxact->fxid); + + if (!TransactionIdIsValid(oldestRunningXid) || + TransactionIdPrecedes(xid, oldestRunningXid)) + oldestRunningXid = xid; + } + + LWLockRelease(TwoPhaseStateLock); + + return oldestRunningXid; +} diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 82cf65fae737a..750d262fccade 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -854,7 +854,17 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, pgstat_create_subscription(subid); - if (opts.enabled) + /* + * Notify the launcher to start the apply worker if the subscription is + * enabled, or to create the conflict detection slot if retain_dead_tuples + * is enabled. + * + * Creating the conflict detection slot is essential even when the + * subscription is not enabled. This ensures that dead tuples are + * retained, which is necessary for accurately identifying the type of + * conflict during replication. + */ + if (opts.enabled || opts.retaindeadtuples) ApplyLauncherWakeupAtCommit(); ObjectAddressSet(myself, SubscriptionRelationId, subid); diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index d3356bc84ee0c..e6da4028d392e 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -1788,6 +1788,32 @@ AllTablesyncsReady(void) return has_subrels && (table_states_not_ready == NIL); } +/* + * Return whether the subscription currently has any relations. + * + * Note: Unlike HasSubscriptionRelations(), this function relies on cached + * information for subscription relations. Additionally, it should not be + * invoked outside of apply or tablesync workers, as MySubscription must be + * initialized first. + */ +bool +HasSubscriptionRelationsCached(void) +{ + bool started_tx; + bool has_subrels; + + /* We need up-to-date subscription tables info here */ + has_subrels = FetchTableStates(&started_tx); + + if (started_tx) + { + CommitTransactionCommand(); + pgstat_report_stat(true); + } + + return has_subrels; +} + /* * Update the two_phase state of the specified subscription in pg_subscription. */ diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index f1ebd63e792ee..c0f6bef5c282c 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -4595,11 +4595,28 @@ wait_for_local_flush(RetainDeadTuplesData *rdt_data) * workers is complex and not worth the effort, so we simply return if not * all tables are in the READY state. * - * It is safe to add new tables with initial states to the subscription - * after this check because any changes applied to these tables should - * have a WAL position greater than the rdt_data->remote_lsn. + * Advancing the transaction ID is necessary even when no tables are + * currently subscribed, to avoid retaining dead tuples unnecessarily. + * While it might seem safe to skip all phases and directly assign + * candidate_xid to oldest_nonremovable_xid during the + * RDT_GET_CANDIDATE_XID phase in such cases, this is unsafe. If users + * concurrently add tables to the subscription, the apply worker may not + * process invalidations in time. Consequently, + * HasSubscriptionRelationsCached() might miss the new tables, leading to + * premature advancement of oldest_nonremovable_xid. + * + * Performing the check during RDT_WAIT_FOR_LOCAL_FLUSH is safe, as + * invalidations are guaranteed to be processed before applying changes + * from newly added tables while waiting for the local flush to reach + * remote_lsn. + * + * Additionally, even if we check for subscription tables during + * RDT_GET_CANDIDATE_XID, they might be dropped before reaching + * RDT_WAIT_FOR_LOCAL_FLUSH. Therefore, it's still necessary to verify + * subscription tables at this stage to prevent unnecessary tuple + * retention. */ - if (!AllTablesyncsReady()) + if (HasSubscriptionRelationsCached() && !AllTablesyncsReady()) { TimestampTz now; diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index e3dce9dc68d04..59822f22b8d06 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -51,6 +51,7 @@ #include "access/timeline.h" #include "access/transam.h" +#include "access/twophase.h" #include "access/xact.h" #include "access/xlog_internal.h" #include "access/xlogreader.h" @@ -2719,6 +2720,7 @@ ProcessStandbyPSRequestMessage(void) { XLogRecPtr lsn = InvalidXLogRecPtr; TransactionId oldestXidInCommit; + TransactionId oldestGXidInCommit; FullTransactionId nextFullXid; FullTransactionId fullOldestXidInCommit; WalSnd *walsnd = MyWalSnd; @@ -2746,6 +2748,16 @@ ProcessStandbyPSRequestMessage(void) * ones replicated. */ oldestXidInCommit = GetOldestActiveTransactionId(true, false); + oldestGXidInCommit = TwoPhaseGetOldestXidInCommit(); + + /* + * Update the oldest xid for standby transmission if an older prepared + * transaction exists and is currently in commit phase. + */ + if (TransactionIdIsValid(oldestGXidInCommit) && + TransactionIdPrecedes(oldestGXidInCommit, oldestXidInCommit)) + oldestXidInCommit = oldestGXidInCommit; + nextFullXid = ReadNextFullTransactionId(); fullOldestXidInCommit = FullTransactionIdFromAllowableAt(nextFullXid, oldestXidInCommit); diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h index 509bdad9a5d55..64463e9f4afb4 100644 --- a/src/include/access/twophase.h +++ b/src/include/access/twophase.h @@ -68,4 +68,6 @@ extern void TwoPhaseTransactionGid(Oid subid, TransactionId xid, char *gid_res, int szgid); extern bool LookupGXactBySubid(Oid subid); +extern TransactionId TwoPhaseGetOldestXidInCommit(void); + #endif /* TWOPHASE_H */ diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h index 62ea1a0058081..de00380261279 100644 --- a/src/include/replication/worker_internal.h +++ b/src/include/replication/worker_internal.h @@ -272,6 +272,7 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid, char *originname, Size szoriginname); extern bool AllTablesyncsReady(void); +extern bool HasSubscriptionRelationsCached(void); extern void UpdateTwoPhaseState(Oid suboid, char new_state); extern void process_syncing_tables(XLogRecPtr current_lsn); diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index 51b23a39fa935..e06429c288fe6 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -386,6 +386,35 @@ .*Remote row \(2, 4\); replica identity full \(2, 2\)/, 'update target row was deleted in tab'); +############################################################################### +# Check that the xmin value of the conflict detection slot can be advanced when +# the subscription has no tables. +############################################################################### + +# Remove the table from the publication +$node_B->safe_psql('postgres', "ALTER PUBLICATION tap_pub_B DROP TABLE tab"); + +$node_A->safe_psql('postgres', + "ALTER SUBSCRIPTION $subname_AB REFRESH PUBLICATION"); + +# Remember the next transaction ID to be assigned +$next_xid = $node_A->safe_psql('postgres', "SELECT txid_current() + 1;"); + +# Confirm that the xmin value is advanced to the latest nextXid. If no +# transactions are running, the apply worker selects nextXid as the candidate +# for the non-removable xid. See GetOldestActiveTransactionId(). +ok( $node_A->poll_query_until( + 'postgres', + "SELECT xmin = $next_xid from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'" + ), + "the xmin value of slot 'pg_conflict_detection' is updated on Node A"); + +# Re-add the table to the publication for further tests +$node_B->safe_psql('postgres', "ALTER PUBLICATION tap_pub_B ADD TABLE tab"); + +$node_A->safe_psql('postgres', + "ALTER SUBSCRIPTION $subname_AB REFRESH PUBLICATION WITH (copy_data = false)"); + ############################################################################### # Check that dead tuple retention stops due to the wait time surpassing # max_retention_duration. From 8191e0c16a0373f851a9f5a8112e3aec105b5276 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 8 Sep 2025 15:52:23 +0900 Subject: [PATCH 08/96] Fix corruption of pgstats shared hashtable due to OOM failures A new pgstats entry is created as a two-step process: - The entry is looked at in the shared hashtable of pgstats, and is inserted if not found. - When not found and inserted, its fields are then initialized. This part include a DSA chunk allocation for the stats data of the new entry. As currently coded, if the DSA chunk allocation fails due to an out-of-memory failure, an ERROR is generated, leaving in the pgstats shared hashtable an inconsistent entry due to the first step, as the entry has already been inserted in the hashtable. These broken entries can then be found by other backends, crashing them. There are only two callers of pgstat_init_entry(), when loading the pgstats file at startup and when creating a new pgstats entry. This commit changes pgstat_init_entry() so as we use dsa_allocate_extended() with DSA_ALLOC_NO_OOM, making it return NULL on allocation failure instead of failing. This way, a backend failing an entry creation can take appropriate cleanup actions in the shared hashtable before throwing an error. Currently, this means removing the entry from the shared hashtable before throwing the error for the allocation failure. Out-of-memory errors unlikely happen in the wild, and we do not bother with back-patches when these are fixed, usually. However, the problem dealt with here is a degree worse as it breaks the shared memory state of pgstats, impacting other processes that may look at an inconsistent entry that a different process has failed to create. Author: Mikhail Kot Discussion: https://postgr.es/m/CAAi9E7jELo5_-sBENftnc2E8XhW2PKZJWfTC3i2y-GMQd2bcqQ@mail.gmail.com Backpatch-through: 15 --- src/backend/utils/activity/pgstat.c | 11 +++++++++ src/backend/utils/activity/pgstat_shmem.c | 28 ++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ffb5b8cce3441..f8e91484e36be 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -1975,6 +1975,17 @@ pgstat_read_statsfile(void) header = pgstat_init_entry(key.kind, p); dshash_release_lock(pgStatLocal.shared_hash, p); + if (header == NULL) + { + /* + * It would be tempting to switch this ERROR to a + * WARNING, but it would mean that all the statistics + * are discarded when the environment fails on OOM. + */ + elog(ERROR, "could not allocate entry %u/%u/%" PRIu64 " of type %c", + key.kind, key.dboid, + key.objid, t); + } if (!read_chunk(fpin, pgstat_get_entry_data(key.kind, header), diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c index 62de347445365..9dc3212f7dd01 100644 --- a/src/backend/utils/activity/pgstat_shmem.c +++ b/src/backend/utils/activity/pgstat_shmem.c @@ -289,6 +289,13 @@ pgstat_detach_shmem(void) * ------------------------------------------------------------ */ +/* + * Initialize entry newly-created. + * + * Returns NULL in the event of an allocation failure, so as callers can + * take cleanup actions as the entry initialized is already inserted in the + * shared hashtable. + */ PgStatShared_Common * pgstat_init_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent) @@ -311,7 +318,12 @@ pgstat_init_entry(PgStat_Kind kind, pg_atomic_init_u32(&shhashent->generation, 0); shhashent->dropped = false; - chunk = dsa_allocate0(pgStatLocal.dsa, pgstat_get_kind_info(kind)->shared_size); + chunk = dsa_allocate_extended(pgStatLocal.dsa, + pgstat_get_kind_info(kind)->shared_size, + DSA_ALLOC_ZERO | DSA_ALLOC_NO_OOM); + if (chunk == InvalidDsaPointer) + return NULL; + shheader = dsa_get_address(pgStatLocal.dsa, chunk); shheader->magic = 0xdeadbeef; @@ -509,6 +521,20 @@ pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create, if (!shfound) { shheader = pgstat_init_entry(kind, shhashent); + if (shheader == NULL) + { + /* + * Failed the allocation of a new entry, so clean up the + * shared hashtable before giving up. + */ + dshash_delete_entry(pgStatLocal.shared_hash, shhashent); + + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while allocating entry %u/%u/%" PRIu64 ".", + key.kind, key.dboid, key.objid))); + } pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); if (created_entry != NULL) From 6456c6e2c4ad1cf9752e09cce37bfcfe2190c5e0 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 8 Sep 2025 11:38:02 +0000 Subject: [PATCH 09/96] Add test to prevent premature removal of conflict-relevant data. A test has been added to ensure that conflict-relevant data is not prematurely removed when a concurrent prepared transaction is being committed on the publisher. This test introduces an injection point that simulates the presence of a prepared transaction in the commit phase, validating that the system correctly delays conflict slot advancement until the transaction is fully committed. Additionally, the test serves as a safeguard for developers, ensuring that the acquisition of the commit timestamp does not occur before marking DELAY_CHKPT_IN_COMMIT in RecordTransactionCommitPrepared. Reported-by: Robert Haas Author: Zhijie Hou Reviewed-by: shveta malik Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/OS9PR01MB16913F67856B0DA2A909788129400A@OS9PR01MB16913.jpnprd01.prod.outlook.com --- src/backend/access/transam/twophase.c | 6 + src/test/subscription/Makefile | 4 +- src/test/subscription/meson.build | 5 +- src/test/subscription/t/035_conflicts.pl | 160 +++++++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 3e20f4487872e..d8e2fce2c99b7 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -103,6 +103,7 @@ #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -2332,12 +2333,17 @@ RecordTransactionCommitPrepared(TransactionId xid, replorigin = (replorigin_session_origin != InvalidRepOriginId && replorigin_session_origin != DoNotReplicateId); + /* Load the injection point before entering the critical section */ + INJECTION_POINT_LOAD("commit-after-delay-checkpoint"); + START_CRIT_SECTION(); /* See notes in RecordTransactionCommit */ Assert((MyProc->delayChkptFlags & DELAY_CHKPT_IN_COMMIT) == 0); MyProc->delayChkptFlags |= DELAY_CHKPT_IN_COMMIT; + INJECTION_POINT_CACHED("commit-after-delay-checkpoint", NULL); + /* * Ensures the DELAY_CHKPT_IN_COMMIT flag write is globally visible before * commit time is written. diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile index 50b65d8f6ea21..9d97e7d5c0d6d 100644 --- a/src/test/subscription/Makefile +++ b/src/test/subscription/Makefile @@ -13,9 +13,11 @@ subdir = src/test/subscription top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -EXTRA_INSTALL = contrib/hstore +EXTRA_INSTALL = contrib/hstore \ + src/test/modules/injection_points export with_icu +export enable_injection_points check: $(prove_check) diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build index 586ffba434e11..20b4e523d9307 100644 --- a/src/test/subscription/meson.build +++ b/src/test/subscription/meson.build @@ -5,7 +5,10 @@ tests += { 'sd': meson.current_source_dir(), 'bd': meson.current_build_dir(), 'tap': { - 'env': {'with_icu': icu.found() ? 'yes' : 'no'}, + 'env': { + 'with_icu': icu.found() ? 'yes' : 'no', + 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no', + }, 'tests': [ 't/001_rep_changes.pl', 't/002_types.pl', diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index e06429c288fe6..db0d5b464e825 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -415,6 +415,166 @@ $node_A->safe_psql('postgres', "ALTER SUBSCRIPTION $subname_AB REFRESH PUBLICATION WITH (copy_data = false)"); +############################################################################### +# Test that publisher's transactions marked with DELAY_CHKPT_IN_COMMIT prevent +# concurrently deleted tuples on the subscriber from being removed. This test +# also acts as a safeguard to prevent developers from moving the commit +# timestamp acquisition before marking DELAY_CHKPT_IN_COMMIT in +# RecordTransactionCommitPrepared. +############################################################################### + +my $injection_points_supported = $node_B->check_extension('injection_points'); + +# This test depends on an injection point to block the prepared transaction +# commit after marking DELAY_CHKPT_IN_COMMIT flag. +if ($injection_points_supported != 0) +{ + $node_B->append_conf('postgresql.conf', + "shared_preload_libraries = 'injection_points' + max_prepared_transactions = 1"); + $node_B->restart; + + # Disable the subscription on Node B for testing only one-way + # replication. + $node_B->psql('postgres', "ALTER SUBSCRIPTION $subname_BA DISABLE;"); + + # Wait for the apply worker to stop + $node_B->poll_query_until('postgres', + "SELECT count(*) = 0 FROM pg_stat_activity WHERE backend_type = 'logical replication apply worker'" + ); + + # Truncate the table to cleanup existing dead rows in the table. Then insert + # a new row. + $node_B->safe_psql( + 'postgres', qq( + TRUNCATE tab; + INSERT INTO tab VALUES(1, 1); + )); + + $node_B->wait_for_catchup($subname_AB); + + # Create the injection_points extension on the publisher node and attach to the + # commit-after-delay-checkpoint injection point. + $node_B->safe_psql( + 'postgres', + "CREATE EXTENSION injection_points; + SELECT injection_points_attach('commit-after-delay-checkpoint', 'wait');" + ); + + # Start a background session on the publisher node to perform an update and + # pause at the injection point. + my $pub_session = $node_B->background_psql('postgres'); + $pub_session->query_until( + qr/starting_bg_psql/, + q{ + \echo starting_bg_psql + BEGIN; + UPDATE tab SET b = 2 WHERE a = 1; + PREPARE TRANSACTION 'txn_with_later_commit_ts'; + COMMIT PREPARED 'txn_with_later_commit_ts'; + } + ); + + # Confirm the update is suspended + $result = + $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1'); + is($result, qq(1|1), 'publisher sees the old row'); + + # Delete the row on the subscriber. The deleted row should be retained due to a + # transaction on the publisher, which is currently marked with the + # DELAY_CHKPT_IN_COMMIT flag. + $node_A->safe_psql('postgres', "DELETE FROM tab WHERE a = 1;"); + + # Get the commit timestamp for the delete + my $sub_ts = $node_A->safe_psql('postgres', + "SELECT timestamp FROM pg_last_committed_xact();"); + + $log_location = -s $node_A->logfile; + + # Confirm that the apply worker keeps requesting publisher status, while + # awaiting the prepared transaction to commit. Thus, the request log should + # appear more than once. + $node_A->wait_for_log( + qr/sending publisher status request message/, + $log_location); + + $log_location = -s $node_A->logfile; + + $node_A->wait_for_log( + qr/sending publisher status request message/, + $log_location); + + # Confirm that the dead tuple cannot be removed + ($cmdret, $stdout, $stderr) = + $node_A->psql('postgres', qq(VACUUM (verbose) public.tab;)); + + ok($stderr =~ qr/1 are dead but not yet removable/, + 'the deleted column is non-removable'); + + $log_location = -s $node_A->logfile; + + # Wakeup and detach the injection point on the publisher node. The prepared + # transaction should now commit. + $node_B->safe_psql( + 'postgres', + "SELECT injection_points_wakeup('commit-after-delay-checkpoint'); + SELECT injection_points_detach('commit-after-delay-checkpoint');" + ); + + # Close the background session on the publisher node + ok($pub_session->quit, "close publisher session"); + + # Confirm that the transaction committed + $result = + $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1'); + is($result, qq(1|2), 'publisher sees the new row'); + + # Ensure the UPDATE is replayed on subscriber + $node_B->wait_for_catchup($subname_AB); + + $logfile = slurp_file($node_A->logfile(), $log_location); + ok( $logfile =~ + qr/conflict detected on relation "public.tab": conflict=update_deleted.* +.*DETAIL:.* The row to be updated was deleted locally in transaction [0-9]+ at .* +.*Remote row \(1, 2\); replica identity full \(1, 1\)/, + 'update target row was deleted in tab'); + + # Remember the next transaction ID to be assigned + $next_xid = + $node_A->safe_psql('postgres', "SELECT txid_current() + 1;"); + + # Confirm that the xmin value is advanced to the latest nextXid after the + # prepared transaction on the publisher has been committed. + ok( $node_A->poll_query_until( + 'postgres', + "SELECT xmin = $next_xid from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'" + ), + "the xmin value of slot 'pg_conflict_detection' is updated on subscriber" + ); + + # Confirm that the dead tuple can be removed now + ($cmdret, $stdout, $stderr) = + $node_A->psql('postgres', qq(VACUUM (verbose) public.tab;)); + + ok($stderr =~ qr/1 removed, 0 remain, 0 are dead but not yet removable/, + 'the deleted column is removed'); + + # Get the commit timestamp for the publisher's update + my $pub_ts = $node_B->safe_psql('postgres', + "SELECT pg_xact_commit_timestamp(xmin) from tab where a=1;"); + + # Check that the commit timestamp for the update on the publisher is later than + # or equal to the timestamp of the local deletion, as the commit timestamp + # should be assigned after marking the DELAY_CHKPT_IN_COMMIT flag. + $result = $node_B->safe_psql('postgres', + "SELECT '$pub_ts'::timestamp >= '$sub_ts'::timestamp"); + is($result, qq(t), + "pub UPDATE's timestamp is later than that of sub's DELETE"); + + # Re-enable the subscription for further tests + $node_B->psql('postgres', "ALTER SUBSCRIPTION $subname_BA ENABLE;"); +} + ############################################################################### # Check that dead tuple retention stops due to the wait time surpassing # max_retention_duration. From 3399c265543ec3cdbeff2fa2900e03b326705f63 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Mon, 8 Sep 2025 10:22:42 -0400 Subject: [PATCH 10/96] Remove unneeded VM pin from VM replay Previously, heap_xlog_visible() called visibilitymap_pin() even after getting a buffer from XLogReadBufferForRedoExtended() -- which returns a pinned buffer containing the specified block of the visibility map. This would just have resulted in visibilitymap_pin() returning early since the specified page was already present and pinned, but it was confusing extraneous code, so remove it. It doesn't seem worth backporting, though. It appears to be an oversight in 2c03216. While we are at it, remove two VM-related redundant asserts in the COPY FREEZE code path. visibilitymap_set() already asserts that PD_ALL_VISIBLE is set on the heap page and checks that the vmbuffer contains the bits corresponding to the specified heap block, so callers do not also need to check this. Author: Melanie Plageman Reported-by: Melanie Plageman Reported-by: Kirill Reshke Reviewed-by: Kirill Reshke Reviewed-by: Andres Freund Discussion: https://postgr.es/m/CALdSSPhu7WZd%2BEfQDha1nz%3DDC93OtY1%3DUFEdWwSZsASka_2eRQ%40mail.gmail.com --- src/backend/access/heap/heapam.c | 3 --- src/backend/access/heap/heapam_xlog.c | 1 - 2 files changed, 4 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index e3e7307ef5f79..4c5ae205a7a60 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -2647,9 +2647,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, */ if (all_frozen_set) { - Assert(PageIsAllVisible(page)); - Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer)); - /* * It's fine to use InvalidTransactionId here - this is only used * when HEAP_INSERT_FROZEN is specified, which intentionally diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c index 5d48f071f53a7..cf843277938de 100644 --- a/src/backend/access/heap/heapam_xlog.c +++ b/src/backend/access/heap/heapam_xlog.c @@ -295,7 +295,6 @@ heap_xlog_visible(XLogReaderState *record) LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); reln = CreateFakeRelcacheEntry(rlocator); - visibilitymap_pin(reln, blkno, &vmbuffer); visibilitymap_set(reln, blkno, InvalidBuffer, lsn, vmbuffer, xlrec->snapshotConflictHorizon, vmbits); From 585e31fcb6dfcb1d88cfee2371f565574db24869 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 8 Sep 2025 11:50:33 -0400 Subject: [PATCH 11/96] Don't generate fake "*SELECT*" or "*SELECT* %d" subquery aliases. rte->alias should point only to a user-written alias, but in these cases that principle was violated. Fixing this causes some regression test output changes: wherever rte->alias previously had a value and is now NULL, rte->eref is now set to a generated name rather than to rte->alias; and the scheme used to generate eref names differs from what we were doing for aliases. The upshot is that instead of "*SELECT*" or "*SELECT* %d", EXPLAIN will now emit "unnamed_subquery" or "unnamed_subquery_%d". But that's a reasonable descriptor, and we were already producing that in yet other cases, so this seems not too objectionable. Author: Tom Lane Co-authored-by: Robert Haas Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- contrib/postgres_fdw/expected/postgres_fdw.out | 8 ++++---- src/backend/executor/functions.c | 2 +- src/backend/parser/analyze.c | 7 ++----- src/test/regress/expected/partition_prune.out | 4 ++-- src/test/regress/expected/rangefuncs.out | 8 ++++---- src/test/regress/expected/union.out | 14 +++++++------- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 78b8367d28935..18d727d77907a 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -5086,13 +5086,13 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE -- =================================================================== EXPLAIN (verbose, costs off) INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Insert on public.ft2 Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) Batch Size: 1 - -> Subquery Scan on "*SELECT*" - Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum + -> Subquery Scan on unnamed_subquery + Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1", NULL::integer, unnamed_subquery."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum -> Foreign Scan on public.ft2 ft2_1 Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 97455b1ed4a5b..630d708d2a3f0 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -2483,7 +2483,7 @@ check_sql_stmt_retval(List *queryTreeList, rte = makeNode(RangeTblEntry); rte->rtekind = RTE_SUBQUERY; rte->subquery = parse; - rte->eref = rte->alias = makeAlias("*SELECT*", colnames); + rte->eref = makeAlias("unnamed_subquery", colnames); rte->lateral = false; rte->inh = false; rte->inFromCl = true; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 34f7c17f576ef..b9763ea17144c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -777,7 +777,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) */ nsitem = addRangeTableEntryForSubquery(pstate, selectQuery, - makeAlias("*SELECT*", NIL), + NULL, false, false); addNSItemToQuery(pstate, nsitem, true, false, false); @@ -2100,7 +2100,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, { /* Process leaf SELECT */ Query *selectQuery; - char selectName[32]; ParseNamespaceItem *nsitem; RangeTblRef *rtr; ListCell *tl; @@ -2156,11 +2155,9 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, /* * Make the leaf query be a subquery in the top-level rangetable. */ - snprintf(selectName, sizeof(selectName), "*SELECT* %d", - list_length(pstate->p_rtable) + 1); nsitem = addRangeTableEntryForSubquery(pstate, selectQuery, - makeAlias(selectName, NIL), + NULL, false, false); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index d1966cd7d829f..68ecd95180920 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4763,7 +4763,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o QUERY PLAN ---------------------------------------------------------------------------------------------- Append - -> Subquery Scan on "*SELECT* 1_1" + -> Subquery Scan on unnamed_subquery_2 -> WindowAgg Window: w1 AS (PARTITION BY part_abc.a ORDER BY part_abc.a) -> Append @@ -4780,7 +4780,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o -> Index Scan using part_abc_3_2_a_idx on part_abc_3_2 part_abc_4 Index Cond: (a >= (stable_one() + 1)) Filter: (d <= stable_one()) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> WindowAgg Window: w1 AS (PARTITION BY part_abc_5.a ORDER BY part_abc_5.a) -> Append diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index c21be83aa4aaf..30241e22da270 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2130,10 +2130,10 @@ select testrngfunc(); explain (verbose, costs off) select * from testrngfunc(); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on "*SELECT*" - Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1" + QUERY PLAN +---------------------------------------------------------------------- + Subquery Scan on unnamed_subquery + Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1" -> Unique Output: (1), (2) -> Sort diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 96962817ed45a..d3ea433db1577 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -942,7 +942,7 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1; ERROR: column "q2" does not exist LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1... ^ -DETAIL: There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query. +DETAIL: There is a column named "q2" in table "unnamed_subquery", but it cannot be referenced from this part of the query. -- But this should work: SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1; q1 @@ -1338,14 +1338,14 @@ where q2 = q2; ---------------------------------------------------------- Unique -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" + Sort Key: unnamed_subquery.q1 + -> Subquery Scan on unnamed_subquery -> Unique -> Sort Sort Key: i81.q1, i81.q2 -> Seq Scan on int8_tbl i81 Filter: (q2 IS NOT NULL) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> Unique -> Sort Sort Key: i82.q1, i82.q2 @@ -1374,14 +1374,14 @@ where -q1 = q2; -------------------------------------------------------- Unique -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" + Sort Key: unnamed_subquery.q1 + -> Subquery Scan on unnamed_subquery -> Unique -> Sort Sort Key: i81.q1, i81.q2 -> Seq Scan on int8_tbl i81 Filter: ((- q1) = q2) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> Unique -> Sort Sort Key: i82.q1, i82.q2 From 6f79024df3461f794aace8bbc8706d8e5f7da091 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 8 Sep 2025 12:24:02 -0400 Subject: [PATCH 12/96] Don't generate fake "ANY_subquery" aliases, either. This is just like the previous commit, but for a different invented alias name. Author: Robert Haas Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- src/backend/optimizer/plan/subselect.c | 2 +- src/test/regress/expected/memoize.out | 8 ++++---- src/test/regress/expected/subselect.out | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index d71ed958e31b3..fae18548e074e 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1397,7 +1397,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink, */ nsitem = addRangeTableEntryForSubquery(pstate, subselect, - makeAlias("ANY_subquery", NIL), + NULL, use_lateral, false); rte = nsitem->p_rte; diff --git a/src/test/regress/expected/memoize.out b/src/test/regress/expected/memoize.out index 150dc1b44cf62..fbcaf113266c5 100644 --- a/src/test/regress/expected/memoize.out +++ b/src/test/regress/expected/memoize.out @@ -545,15 +545,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM tab_anti t1 WHERE t1.a IN (SELECT a FROM tab_anti t2 WHERE t2.b IN (SELECT t1.b FROM tab_anti t3 WHERE t2.a > 1 OFFSET 0)); - QUERY PLAN -------------------------------------------------- + QUERY PLAN +--------------------------------------------------- Nested Loop Semi Join -> Seq Scan on tab_anti t1 -> Nested Loop Semi Join Join Filter: (t1.a = t2.a) -> Seq Scan on tab_anti t2 - -> Subquery Scan on "ANY_subquery" - Filter: (t2.b = "ANY_subquery".b) + -> Subquery Scan on unnamed_subquery + Filter: (t2.b = unnamed_subquery.b) -> Result One-Time Filter: (t2.a > 1) -> Seq Scan on tab_anti t3 diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index c16dff05bc12e..7a1c216a0b1b7 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1692,14 +1692,14 @@ select * from int4_tbl o where (f1, f1) in ------------------------------------------------------------------- Nested Loop Semi Join Output: o.f1 - Join Filter: (o.f1 = "ANY_subquery".f1) + Join Filter: (o.f1 = unnamed_subquery.f1) -> Seq Scan on public.int4_tbl o Output: o.f1 -> Materialize - Output: "ANY_subquery".f1, "ANY_subquery".g - -> Subquery Scan on "ANY_subquery" - Output: "ANY_subquery".f1, "ANY_subquery".g - Filter: ("ANY_subquery".f1 = "ANY_subquery".g) + Output: unnamed_subquery.f1, unnamed_subquery.g + -> Subquery Scan on unnamed_subquery + Output: unnamed_subquery.f1, unnamed_subquery.g + Filter: (unnamed_subquery.f1 = unnamed_subquery.g) -> Result Output: i.f1, ((generate_series(1, 50)) / 10) -> ProjectSet @@ -2867,8 +2867,8 @@ ON B.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd); -> Memoize Cache Key: b.hundred, b.odd Cache Mode: binary - -> Subquery Scan on "ANY_subquery" - Filter: (b.hundred = "ANY_subquery".min) + -> Subquery Scan on unnamed_subquery + Filter: (b.hundred = unnamed_subquery.min) -> Result InitPlan 1 -> Limit From 5a170e992a4d402ef0e1b8ce7284cd78879ece73 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 8 Sep 2025 12:58:07 -0400 Subject: [PATCH 13/96] Don't generate fake "*TLOCRN*" or "*TROCRN*" aliases, either. This is just like the previous two commits, except that this fix actually doesn't change any regression test outputs. Author: Robert Haas Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- src/backend/rewrite/rewriteSearchCycle.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/rewrite/rewriteSearchCycle.c b/src/backend/rewrite/rewriteSearchCycle.c index 9f95d4dc1b0e8..5202ef43d1068 100644 --- a/src/backend/rewrite/rewriteSearchCycle.c +++ b/src/backend/rewrite/rewriteSearchCycle.c @@ -282,8 +282,8 @@ rewriteSearchAndCycle(CommonTableExpr *cte) newrte = makeNode(RangeTblEntry); newrte->rtekind = RTE_SUBQUERY; - newrte->alias = makeAlias("*TLOCRN*", cte->ctecolnames); - newrte->eref = newrte->alias; + newrte->alias = NULL; + newrte->eref = makeAlias("*TLOCRN*", cte->ctecolnames); newsubquery = copyObject(rte1->subquery); IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); newrte->subquery = newsubquery; @@ -379,8 +379,8 @@ rewriteSearchAndCycle(CommonTableExpr *cte) ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column)); ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column)); } - newrte->alias = makeAlias("*TROCRN*", ewcl); - newrte->eref = newrte->alias; + newrte->alias = NULL; + newrte->eref = makeAlias("*TROCRN*", ewcl); /* * Find the reference to the recursive CTE in the right UNION subquery's From 4b5f206de2bb9152a99a5c218caf2580cc5a0e9e Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Mon, 8 Sep 2025 14:25:10 -0400 Subject: [PATCH 14/96] Remove unused xl_heap_prune member, reason f83d709760d8 refactored xl_heap_prune and added an unused member, reason. While PruneReason is used when constructing this WAL record to set the WAL record definition, it doesn't need to be stored in a separate field in the record. Remove it. We won't backport this, since modifying an exposed struct definition to remove an unused field would do more harm than good. Author: Melanie Plageman Reported-by: Andres Freund Reviewed-by: Robert Haas Discussion: https://postgr.es/m/tvvtfoxz5ykpsctxjbzxg3nldnzfc7geplrt2z2s54pmgto27y%40hbijsndifu45 --- src/include/access/heapam_xlog.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index 277df6b3cf0b3..d4c0625b63228 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -284,7 +284,6 @@ typedef struct xl_heap_update */ typedef struct xl_heap_prune { - uint8 reason; uint8 flags; /* From 3bcfcd815e1a2d51772ba27e0d034467f0344f15 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 8 Sep 2025 14:19:48 -0500 Subject: [PATCH 15/96] pg_upgrade: Transfer pg_largeobject_metadata's files when possible. Commit 161a3e8b68 taught pg_upgrade to use COPY for large object metadata for upgrades from v12 and newer, which is much faster to restore than the proper large object commands. For upgrades from v16 and newer, we can take this a step further and transfer the large object metadata files as if they were user tables. We can't transfer the files from older versions because the aclitem data type (needed by pg_largeobject_metadata.lomacl) changed its storage format in v16 (see commit 7b378237aa). Note that this commit is essentially a revert of commit 12a53c732c. There are a couple of caveats. First, we still need to COPY the corresponding pg_shdepend rows for large objects. Second, we need to COPY anything in pg_largeobject_metadata with a comment or security label, else restoring those will fail. This means that an upgrade in which every large object has a comment or security label won't gain anything from this commit, but it should at least avoid making those unusual use-cases any worse. pg_upgrade must also take care to transfer the relfilenodes of pg_largeobject_metadata and its index, as was done for pg_largeobject in commits d498e052b4 and bbe08b8869. Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/aJ3_Gih_XW1_O2HF%40nathan --- src/backend/commands/tablecmds.c | 12 ++-- src/bin/pg_dump/pg_dump.c | 80 ++++++++++++++++++---- src/bin/pg_upgrade/Makefile | 3 +- src/bin/pg_upgrade/info.c | 11 ++- src/bin/pg_upgrade/pg_upgrade.c | 6 +- src/bin/pg_upgrade/t/006_transfer_modes.pl | 67 ++++++++++++++++++ 6 files changed, 154 insertions(+), 25 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d621e..3be2e051d32fb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -42,6 +42,7 @@ #include "catalog/pg_foreign_table.h" #include "catalog/pg_inherits.h" #include "catalog/pg_largeobject.h" +#include "catalog/pg_largeobject_metadata.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_policy.h" @@ -2389,12 +2390,15 @@ truncate_check_rel(Oid relid, Form_pg_class reltuple) /* * Most system catalogs can't be truncated at all, or at least not unless * allow_system_table_mods=on. As an exception, however, we allow - * pg_largeobject to be truncated as part of pg_upgrade, because we need - * to change its relfilenode to match the old cluster, and allowing a - * TRUNCATE command to be executed is the easiest way of doing that. + * pg_largeobject and pg_largeobject_metadata to be truncated as part of + * pg_upgrade, because we need to change its relfilenode to match the old + * cluster, and allowing a TRUNCATE command to be executed is the easiest + * way of doing that. */ if (!allowSystemTableMods && IsSystemClass(relid, reltuple) - && (!IsBinaryUpgrade || relid != LargeObjectRelationId)) + && (!IsBinaryUpgrade || + (relid != LargeObjectRelationId && + relid != LargeObjectMetadataRelationId))) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied: \"%s\" is a system catalog", diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index bea793456f969..b4c45ad803e94 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1131,6 +1131,23 @@ main(int argc, char **argv) shdepend->dataObj->filtercond = "WHERE classid = 'pg_largeobject'::regclass " "AND dbid = (SELECT oid FROM pg_database " " WHERE datname = current_database())"; + + /* + * If upgrading from v16 or newer, only dump large objects with + * comments/seclabels. For these upgrades, pg_upgrade can copy/link + * pg_largeobject_metadata's files (which is usually faster) but we + * still need to dump LOs with comments/seclabels here so that the + * subsequent COMMENT and SECURITY LABEL commands work. pg_upgrade + * can't copy/link the files from older versions because aclitem + * (needed by pg_largeobject_metadata.lomacl) changed its storage + * format in v16. + */ + if (fout->remoteVersion >= 160000) + lo_metadata->dataObj->filtercond = "WHERE oid IN " + "(SELECT objoid FROM pg_description " + "WHERE classoid = " CppAsString2(LargeObjectRelationId) " " + "UNION SELECT objoid FROM pg_seclabel " + "WHERE classoid = " CppAsString2(LargeObjectRelationId) ")"; } /* @@ -3629,26 +3646,32 @@ dumpDatabase(Archive *fout) /* * pg_largeobject comes from the old system intact, so set its * relfrozenxids, relminmxids and relfilenode. + * + * pg_largeobject_metadata also comes from the old system intact for + * upgrades from v16 and newer, so set its relfrozenxids, relminmxids, and + * relfilenode, too. pg_upgrade can't copy/link the files from older + * versions because aclitem (needed by pg_largeobject_metadata.lomacl) + * changed its storage format in v16. */ if (dopt->binary_upgrade) { PGresult *lo_res; PQExpBuffer loFrozenQry = createPQExpBuffer(); PQExpBuffer loOutQry = createPQExpBuffer(); + PQExpBuffer lomOutQry = createPQExpBuffer(); PQExpBuffer loHorizonQry = createPQExpBuffer(); + PQExpBuffer lomHorizonQry = createPQExpBuffer(); int ii_relfrozenxid, ii_relfilenode, ii_oid, ii_relminmxid; - /* - * pg_largeobject - */ if (fout->remoteVersion >= 90300) appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, relminmxid, relfilenode, oid\n" "FROM pg_catalog.pg_class\n" - "WHERE oid IN (%u, %u);\n", - LargeObjectRelationId, LargeObjectLOidPNIndexId); + "WHERE oid IN (%u, %u, %u, %u);\n", + LargeObjectRelationId, LargeObjectLOidPNIndexId, + LargeObjectMetadataRelationId, LargeObjectMetadataOidIndexId); else appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, 0 AS relminmxid, relfilenode, oid\n" "FROM pg_catalog.pg_class\n" @@ -3663,35 +3686,57 @@ dumpDatabase(Archive *fout) ii_oid = PQfnumber(lo_res, "oid"); appendPQExpBufferStr(loHorizonQry, "\n-- For binary upgrade, set pg_largeobject relfrozenxid and relminmxid\n"); + appendPQExpBufferStr(lomHorizonQry, "\n-- For binary upgrade, set pg_largeobject_metadata relfrozenxid and relminmxid\n"); appendPQExpBufferStr(loOutQry, "\n-- For binary upgrade, preserve pg_largeobject and index relfilenodes\n"); + appendPQExpBufferStr(lomOutQry, "\n-- For binary upgrade, preserve pg_largeobject_metadata and index relfilenodes\n"); for (int i = 0; i < PQntuples(lo_res); ++i) { Oid oid; RelFileNumber relfilenumber; + PQExpBuffer horizonQry; + PQExpBuffer outQry; + + oid = atooid(PQgetvalue(lo_res, i, ii_oid)); + relfilenumber = atooid(PQgetvalue(lo_res, i, ii_relfilenode)); - appendPQExpBuffer(loHorizonQry, "UPDATE pg_catalog.pg_class\n" + if (oid == LargeObjectRelationId || + oid == LargeObjectLOidPNIndexId) + { + horizonQry = loHorizonQry; + outQry = loOutQry; + } + else + { + horizonQry = lomHorizonQry; + outQry = lomOutQry; + } + + appendPQExpBuffer(horizonQry, "UPDATE pg_catalog.pg_class\n" "SET relfrozenxid = '%u', relminmxid = '%u'\n" "WHERE oid = %u;\n", atooid(PQgetvalue(lo_res, i, ii_relfrozenxid)), atooid(PQgetvalue(lo_res, i, ii_relminmxid)), atooid(PQgetvalue(lo_res, i, ii_oid))); - oid = atooid(PQgetvalue(lo_res, i, ii_oid)); - relfilenumber = atooid(PQgetvalue(lo_res, i, ii_relfilenode)); - - if (oid == LargeObjectRelationId) - appendPQExpBuffer(loOutQry, + if (oid == LargeObjectRelationId || + oid == LargeObjectMetadataRelationId) + appendPQExpBuffer(outQry, "SELECT pg_catalog.binary_upgrade_set_next_heap_relfilenode('%u'::pg_catalog.oid);\n", relfilenumber); - else if (oid == LargeObjectLOidPNIndexId) - appendPQExpBuffer(loOutQry, + else if (oid == LargeObjectLOidPNIndexId || + oid == LargeObjectMetadataOidIndexId) + appendPQExpBuffer(outQry, "SELECT pg_catalog.binary_upgrade_set_next_index_relfilenode('%u'::pg_catalog.oid);\n", relfilenumber); } appendPQExpBufferStr(loOutQry, "TRUNCATE pg_catalog.pg_largeobject;\n"); + appendPQExpBufferStr(lomOutQry, + "TRUNCATE pg_catalog.pg_largeobject_metadata;\n"); + appendPQExpBufferStr(loOutQry, loHorizonQry->data); + appendPQExpBufferStr(lomOutQry, lomHorizonQry->data); ArchiveEntry(fout, nilCatalogId, createDumpId(), ARCHIVE_OPTS(.tag = "pg_largeobject", @@ -3699,11 +3744,20 @@ dumpDatabase(Archive *fout) .section = SECTION_PRE_DATA, .createStmt = loOutQry->data)); + if (fout->remoteVersion >= 160000) + ArchiveEntry(fout, nilCatalogId, createDumpId(), + ARCHIVE_OPTS(.tag = "pg_largeobject_metadata", + .description = "pg_largeobject_metadata", + .section = SECTION_PRE_DATA, + .createStmt = lomOutQry->data)); + PQclear(lo_res); destroyPQExpBuffer(loFrozenQry); destroyPQExpBuffer(loHorizonQry); + destroyPQExpBuffer(lomHorizonQry); destroyPQExpBuffer(loOutQry); + destroyPQExpBuffer(lomOutQry); } PQclear(res); diff --git a/src/bin/pg_upgrade/Makefile b/src/bin/pg_upgrade/Makefile index f83d2b5d30955..69fcf593caec9 100644 --- a/src/bin/pg_upgrade/Makefile +++ b/src/bin/pg_upgrade/Makefile @@ -3,8 +3,7 @@ PGFILEDESC = "pg_upgrade - an in-place binary upgrade utility" PGAPPICON = win32 -# required for 003_upgrade_logical_replication_slots.pl -EXTRA_INSTALL=contrib/test_decoding +EXTRA_INSTALL=contrib/test_decoding src/test/modules/dummy_seclabel subdir = src/bin/pg_upgrade top_builddir = ../../.. diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c index c39eb077c2fae..7ce0827016803 100644 --- a/src/bin/pg_upgrade/info.c +++ b/src/bin/pg_upgrade/info.c @@ -498,7 +498,10 @@ get_rel_infos_query(void) * * pg_largeobject contains user data that does not appear in pg_dump * output, so we have to copy that system table. It's easiest to do that - * by treating it as a user table. + * by treating it as a user table. We can do the same for + * pg_largeobject_metadata for upgrades from v16 and newer. pg_upgrade + * can't copy/link the files from older versions because aclitem (needed + * by pg_largeobject_metadata.lomacl) changed its storage format in v16. */ appendPQExpBuffer(&query, "WITH regular_heap (reloid, indtable, toastheap) AS ( " @@ -514,10 +517,12 @@ get_rel_infos_query(void) " 'binary_upgrade', 'pg_toast') AND " " c.oid >= %u::pg_catalog.oid) OR " " (n.nspname = 'pg_catalog' AND " - " relname IN ('pg_largeobject') ))), ", + " relname IN ('pg_largeobject'%s) ))), ", (user_opts.transfer_mode == TRANSFER_MODE_SWAP) ? ", " CppAsString2(RELKIND_SEQUENCE) : "", - FirstNormalObjectId); + FirstNormalObjectId, + (GET_MAJOR_VERSION(old_cluster.major_version) >= 1600) ? + ", 'pg_largeobject_metadata'" : ""); /* * Add a CTE that collects OIDs of toast tables belonging to the tables diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index d5cd5bf0b3a6b..490e98fa26f2a 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -29,9 +29,9 @@ * We control all assignments of pg_enum.oid because these oids are stored * in user tables as enum values. * - * We control all assignments of pg_authid.oid for historical reasons (the - * oids used to be stored in pg_largeobject_metadata, which is now copied via - * SQL commands), that might change at some point in the future. + * We control all assignments of pg_authid.oid because the oids are stored in + * pg_largeobject_metadata, which is copied via file transfer for upgrades + * from v16 and newer. * * We control all assignments of pg_database.oid because we want the directory * names to match between the old and new cluster. diff --git a/src/bin/pg_upgrade/t/006_transfer_modes.pl b/src/bin/pg_upgrade/t/006_transfer_modes.pl index 348f402146234..2f68f0b56aa61 100644 --- a/src/bin/pg_upgrade/t/006_transfer_modes.pl +++ b/src/bin/pg_upgrade/t/006_transfer_modes.pl @@ -45,6 +45,22 @@ sub test_mode $old->append_conf('postgresql.conf', "allow_in_place_tablespaces = true"); } + # We can only test security labels if both the old and new installations + # have dummy_seclabel. + my $test_seclabel = 1; + $old->start; + if (!$old->check_extension('dummy_seclabel')) + { + $test_seclabel = 0; + } + $old->stop; + $new->start; + if (!$new->check_extension('dummy_seclabel')) + { + $test_seclabel = 0; + } + $new->stop; + # Create a small variety of simple test objects on the old cluster. We'll # check that these reach the new version after upgrading. $old->start; @@ -83,6 +99,29 @@ sub test_mode $old->safe_psql('testdb3', "CREATE TABLE test6 AS SELECT generate_series(607, 711)"); } + + # While we are here, test handling of large objects. + $old->safe_psql('postgres', q| + CREATE ROLE regress_lo_1; + CREATE ROLE regress_lo_2; + + SELECT lo_from_bytea(4532, '\xffffff00'); + COMMENT ON LARGE OBJECT 4532 IS 'test'; + + SELECT lo_from_bytea(4533, '\x0f0f0f0f'); + ALTER LARGE OBJECT 4533 OWNER TO regress_lo_1; + GRANT SELECT ON LARGE OBJECT 4533 TO regress_lo_2; + |); + + if ($test_seclabel) + { + $old->safe_psql('postgres', q| + CREATE EXTENSION dummy_seclabel; + + SELECT lo_from_bytea(4534, '\x00ffffff'); + SECURITY LABEL ON LARGE OBJECT 4534 IS 'classified'; + |); + } $old->stop; my $result = command_ok_or_fails_like( @@ -132,6 +171,34 @@ sub test_mode $result = $new->safe_psql('testdb3', "SELECT COUNT(*) FROM test6"); is($result, '105', "test6 data after pg_upgrade $mode"); } + + # Tests for large objects + $result = $new->safe_psql('postgres', "SELECT lo_get(4532)"); + is($result, '\xffffff00', "LO contents after upgrade"); + $result = $new->safe_psql('postgres', + "SELECT obj_description(4532, 'pg_largeobject')"); + is($result, 'test', "comment on LO after pg_upgrade"); + + $result = $new->safe_psql('postgres', "SELECT lo_get(4533)"); + is($result, '\x0f0f0f0f', "LO contents after upgrade"); + $result = $new->safe_psql('postgres', + "SELECT lomowner::regrole FROM pg_largeobject_metadata WHERE oid = 4533"); + is($result, 'regress_lo_1', "LO owner after upgrade"); + $result = $new->safe_psql('postgres', + "SELECT lomacl FROM pg_largeobject_metadata WHERE oid = 4533"); + is($result, '{regress_lo_1=rw/regress_lo_1,regress_lo_2=r/regress_lo_1}', + "LO ACL after upgrade"); + + if ($test_seclabel) + { + $result = $new->safe_psql('postgres', "SELECT lo_get(4534)"); + is($result, '\x00ffffff', "LO contents after upgrade"); + $result = $new->safe_psql('postgres', q| + SELECT label FROM pg_seclabel WHERE objoid = 4534 + AND classoid = 'pg_largeobject'::regclass + |); + is($result, 'classified', "seclabel on LO after pg_upgrade"); + } $new->stop; } From 9af672bcb245950e58198119ba6eb17043fd3a6d Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Mon, 8 Sep 2025 12:29:42 -0700 Subject: [PATCH 16/96] meson: build checksums with extra optimization flags. Use -funroll-loops and -ftree-vectorize when building checksum.c to match what autoconf does. Discussion: https://postgr.es/m/a81f2f7ef34afc24a89c613671ea017e3651329c.camel@j-davis.com Reviewed-by: Andres Freund --- src/backend/storage/page/meson.build | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/storage/page/meson.build b/src/backend/storage/page/meson.build index c3e4a805862a9..112f00ff36552 100644 --- a/src/backend/storage/page/meson.build +++ b/src/backend/storage/page/meson.build @@ -1,7 +1,15 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group +checksum_backend_lib = static_library('checksum_backend_lib', + 'checksum.c', + dependencies: backend_build_deps, + kwargs: internal_lib_args, + c_args: vectorize_cflags + unroll_loops_cflags, +) + +backend_link_with += checksum_backend_lib + backend_sources += files( 'bufpage.c', - 'checksum.c', 'itemptr.c', ) From 8ec97e78a7713a1ebf4976b55c19f6c9bc2716d9 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Mon, 8 Sep 2025 17:13:31 -0400 Subject: [PATCH 17/96] Add error codes when vacuum discovers VM corruption Commit fd6ec93bf890314a and other previous work established the principle that when an error is potentially reachable in case of on-disk corruption but is not expected to be reached otherwise, ERRCODE_DATA_CORRUPTED should be used. This allows log monitoring software to search for evidence of corruption by filtering on the error code. Enhance the existing log messages emitted when the heap page is found to be inconsistent with the VM by adding this error code. Suggested-by: Andrey Borodin Author: Melanie Plageman Reviewed-by: Robert Haas Discussion: https://postgr.es/m/87DD95AA-274F-4F4F-BAD9-7738E5B1F905%40yandex-team.ru --- src/backend/access/heap/vacuumlazy.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 932701d8420dc..981d9380a925c 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -2121,8 +2121,11 @@ lazy_scan_prune(LVRelState *vacrel, else if (all_visible_according_to_vm && !PageIsAllVisible(page) && visibilitymap_get_status(vacrel->rel, blkno, &vmbuffer) != 0) { - elog(WARNING, "page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u", - vacrel->relname, blkno); + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u", + vacrel->relname, blkno))); + visibilitymap_clear(vacrel->rel, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS); } @@ -2143,8 +2146,11 @@ lazy_scan_prune(LVRelState *vacrel, */ else if (presult.lpdead_items > 0 && PageIsAllVisible(page)) { - elog(WARNING, "page containing LP_DEAD items is marked as all-visible in relation \"%s\" page %u", - vacrel->relname, blkno); + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("page containing LP_DEAD items is marked as all-visible in relation \"%s\" page %u", + vacrel->relname, blkno))); + PageClearAllVisible(page); MarkBufferDirty(buf); visibilitymap_clear(vacrel->rel, blkno, vmbuffer, From 5ac3c1ac22cb325844d0bee37f79f2c11931b32e Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Tue, 9 Sep 2025 03:18:22 +0000 Subject: [PATCH 18/96] Fix Coverity issue reported in commit a850be2fe. Address a potential SIGSEGV that may occur when the tablesync worker attempts to locate a deleted row while applying changes. This situation arises during conflict detection for update-deleted scenarios. To prevent this crash, ensure that the operation is errored out early if the leader apply worker is unavailable. Since the leader worker maintains the necessary conflict detection metadata, proceeding without it serves no purpose and risks reporting incorrect conflict type. In the passing, improve a nearby comment. Reported by Tom Lane as per Coverity Author: shveta malik Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/334468.1757280992@sss.pgh.pa.us --- src/backend/replication/logical/worker.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index c0f6bef5c282c..b3cac1023731a 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -3266,12 +3266,18 @@ FindDeletedTupleInLocalRel(Relation localrel, Oid localidxoid, /* * Obtain the information from the leader apply worker as only the - * leader manages conflict retention (see + * leader manages oldest_nonremovable_xid (see * maybe_advance_nonremovable_xid() for details). */ LWLockAcquire(LogicalRepWorkerLock, LW_SHARED); leader = logicalrep_worker_find(MyLogicalRepWorker->subid, InvalidOid, false); + if (!leader) + { + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not detect conflict as the leader apply worker has exited"))); + } SpinLockAcquire(&leader->relmutex); oldestxmin = leader->oldest_nonremovable_xid; From faf071b553830d39fc583beabcaf56ed65259acc Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Tue, 9 Sep 2025 10:39:30 +0100 Subject: [PATCH 19/96] Add date and timestamp variants of random(min, max). This adds 3 new variants of the random() function: random(min date, max date) returns date random(min timestamp, max timestamp) returns timestamp random(min timestamptz, max timestamptz) returns timestamptz Each returns a random value x in the range min <= x <= max. Author: Damien Clochard Reviewed-by: Greg Sabino Mullane Reviewed-by: Dean Rasheed Reviewed-by: Vik Fearing Reviewed-by: Chao Li Discussion: https://postgr.es/m/f524d8cab5914613d9e624d9ce177d3d@dalibo.info --- doc/src/sgml/func/func-datetime.sgml | 30 +++++++ doc/src/sgml/func/func-math.sgml | 3 +- src/backend/utils/adt/pseudorandomfuncs.c | 104 ++++++++++++++++++++-- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.dat | 12 +++ src/test/regress/expected/random.out | 87 ++++++++++++++++++ src/test/regress/sql/random.sql | 26 ++++++ 7 files changed, 254 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/func/func-datetime.sgml b/doc/src/sgml/func/func-datetime.sgml index 482fe45f42ebc..98dd60aa9a7ec 100644 --- a/doc/src/sgml/func/func-datetime.sgml +++ b/doc/src/sgml/func/func-datetime.sgml @@ -928,6 +928,36 @@ + + + + random + + random ( min date, max date ) + date + + + random ( min timestamp, max timestamp ) + timestamp + + + random ( min timestamptz, max timestamptz ) + timestamptz + + + Returns a random value in the range + min <= x <= max. + + + random('1979-02-08'::date,'2025-07-03'::date) + 1983-04-21 + + + random('2000-01-01'::timestamptz, now()) + 2015-09-27 09:11:33.732707+00 + + + diff --git a/doc/src/sgml/func/func-math.sgml b/doc/src/sgml/func/func-math.sgml index 7528dc4cea4b9..fd821c0e70677 100644 --- a/doc/src/sgml/func/func-math.sgml +++ b/doc/src/sgml/func/func-math.sgml @@ -1151,7 +1151,8 @@ The random() and random_normal() - functions listed in use a + functions listed in and + use a deterministic pseudo-random number generator. It is fast but not suitable for cryptographic applications; see the module for a more diff --git a/src/backend/utils/adt/pseudorandomfuncs.c b/src/backend/utils/adt/pseudorandomfuncs.c index e7b8045f92508..1d2a981491bf5 100644 --- a/src/backend/utils/adt/pseudorandomfuncs.c +++ b/src/backend/utils/adt/pseudorandomfuncs.c @@ -17,6 +17,7 @@ #include "common/pg_prng.h" #include "miscadmin.h" +#include "utils/date.h" #include "utils/fmgrprotos.h" #include "utils/numeric.h" #include "utils/timestamp.h" @@ -25,6 +26,18 @@ static pg_prng_state prng_state; static bool prng_seed_set = false; +/* + * Macro for checking the range bounds of random(min, max) functions. Throws + * an error if they're the wrong way round. + */ +#define CHECK_RANGE_BOUNDS(rmin, rmax) \ + do { \ + if ((rmin) > (rmax)) \ + ereport(ERROR, \ + errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("lower bound must be less than or equal to upper bound")); \ + } while (0) + /* * initialize_prng() - * @@ -129,10 +142,7 @@ int4random(PG_FUNCTION_ARGS) int32 rmax = PG_GETARG_INT32(1); int32 result; - if (rmin > rmax) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lower bound must be less than or equal to upper bound")); + CHECK_RANGE_BOUNDS(rmin, rmax); initialize_prng(); @@ -153,10 +163,7 @@ int8random(PG_FUNCTION_ARGS) int64 rmax = PG_GETARG_INT64(1); int64 result; - if (rmin > rmax) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lower bound must be less than or equal to upper bound")); + CHECK_RANGE_BOUNDS(rmin, rmax); initialize_prng(); @@ -177,9 +184,90 @@ numeric_random(PG_FUNCTION_ARGS) Numeric rmax = PG_GETARG_NUMERIC(1); Numeric result; + /* Leave range bound checking to random_numeric() */ + initialize_prng(); result = random_numeric(&prng_state, rmin, rmax); PG_RETURN_NUMERIC(result); } + + +/* + * date_random() - + * + * Returns a random date chosen uniformly in the specified range. + */ +Datum +date_random(PG_FUNCTION_ARGS) +{ + int32 rmin = (int32) PG_GETARG_DATEADT(0); + int32 rmax = (int32) PG_GETARG_DATEADT(1); + DateADT result; + + CHECK_RANGE_BOUNDS(rmin, rmax); + + if (DATE_IS_NOBEGIN(rmin) || DATE_IS_NOEND(rmax)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower and upper bounds must be finite")); + + initialize_prng(); + + result = (DateADT) pg_prng_int64_range(&prng_state, rmin, rmax); + + PG_RETURN_DATEADT(result); +} + +/* + * timestamp_random() - + * + * Returns a random timestamp chosen uniformly in the specified range. + */ +Datum +timestamp_random(PG_FUNCTION_ARGS) +{ + int64 rmin = (int64) PG_GETARG_TIMESTAMP(0); + int64 rmax = (int64) PG_GETARG_TIMESTAMP(1); + Timestamp result; + + CHECK_RANGE_BOUNDS(rmin, rmax); + + if (TIMESTAMP_IS_NOBEGIN(rmin) || TIMESTAMP_IS_NOEND(rmax)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower and upper bounds must be finite")); + + initialize_prng(); + + result = (Timestamp) pg_prng_int64_range(&prng_state, rmin, rmax); + + PG_RETURN_TIMESTAMP(result); +} + +/* + * timestamptz_random() - + * + * Returns a random timestamptz chosen uniformly in the specified range. + */ +Datum +timestamptz_random(PG_FUNCTION_ARGS) +{ + int64 rmin = (int64) PG_GETARG_TIMESTAMPTZ(0); + int64 rmax = (int64) PG_GETARG_TIMESTAMPTZ(1); + TimestampTz result; + + CHECK_RANGE_BOUNDS(rmin, rmax); + + if (TIMESTAMP_IS_NOBEGIN(rmin) || TIMESTAMP_IS_NOEND(rmax)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower and upper bounds must be finite")); + + initialize_prng(); + + result = (TimestampTz) pg_prng_int64_range(&prng_state, rmin, rmax); + + PG_RETURN_TIMESTAMPTZ(result); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 836369f163ef5..ef0d0f92165eb 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202509021 +#define CATALOG_VERSION_NO 202509091 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace0e..03e82d28c8767 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3503,6 +3503,18 @@ proname => 'random', provolatile => 'v', proparallel => 'r', prorettype => 'numeric', proargtypes => 'numeric numeric', proargnames => '{min,max}', prosrc => 'numeric_random' }, +{ oid => '6431', descr => 'random date in range', + proname => 'random', provolatile => 'v', proparallel => 'r', + prorettype => 'date', proargtypes => 'date date', + proargnames => '{min,max}', prosrc => 'date_random' }, +{ oid => '6432', descr => 'random timestamp in range', + proname => 'random', provolatile => 'v', proparallel => 'r', + prorettype => 'timestamp', proargtypes => 'timestamp timestamp', + proargnames => '{min,max}', prosrc => 'timestamp_random' }, +{ oid => '6433', descr => 'random timestamptz in range', + proname => 'random', provolatile => 'v', proparallel => 'r', + prorettype => 'timestamptz', proargtypes => 'timestamptz timestamptz', + proargnames => '{min,max}', prosrc => 'timestamptz_random' }, { oid => '1599', descr => 'set random seed', proname => 'setseed', provolatile => 'v', proparallel => 'r', prorettype => 'void', proargtypes => 'float8', prosrc => 'setseed' }, diff --git a/src/test/regress/expected/random.out b/src/test/regress/expected/random.out index 43cf88a36341b..7f17b2a1b12f8 100644 --- a/src/test/regress/expected/random.out +++ b/src/test/regress/expected/random.out @@ -536,3 +536,90 @@ SELECT n, random(0, trim_scale(abs(1 - 10.0^(-n)))) FROM generate_series(-20, 20 20 | 0.60795101234744211935 (41 rows) +-- random dates +SELECT random('1979-02-08'::date,'2025-07-03'::date) AS random_date_multiple_years; + random_date_multiple_years +---------------------------- + 04-09-1986 +(1 row) + +SELECT random('4714-11-24 BC'::date,'5874897-12-31 AD'::date) AS random_date_maximum_range; + random_date_maximum_range +--------------------------- + 10-02-2898131 +(1 row) + +SELECT random('1979-02-08'::date,'1979-02-08'::date) AS random_date_empty_range; + random_date_empty_range +------------------------- + 02-08-1979 +(1 row) + +SELECT random('2024-12-31'::date, '2024-01-01'::date); -- fail +ERROR: lower bound must be less than or equal to upper bound +SELECT random('-infinity'::date, '2024-01-01'::date); -- fail +ERROR: lower and upper bounds must be finite +SELECT random('2024-12-31'::date, 'infinity'::date); -- fail +ERROR: lower and upper bounds must be finite +-- random timestamps +SELECT random('1979-02-08'::timestamp,'2025-07-03'::timestamp) AS random_timestamp_multiple_years; + random_timestamp_multiple_years +--------------------------------- + Fri Jan 27 18:52:05.366009 2017 +(1 row) + +SELECT random('4714-11-24 BC'::timestamp,'294276-12-31 23:59:59.999999'::timestamp) AS random_timestamp_maximum_range; + random_timestamp_maximum_range +----------------------------------- + Wed Mar 28 00:45:36.180395 226694 +(1 row) + +SELECT random('2024-07-01 12:00:00.000001'::timestamp, '2024-07-01 12:00:00.999999'::timestamp) AS random_narrow_range; + random_narrow_range +--------------------------------- + Mon Jul 01 12:00:00.999286 2024 +(1 row) + +SELECT random('1979-02-08'::timestamp,'1979-02-08'::timestamp) AS random_timestamp_empty_range; + random_timestamp_empty_range +------------------------------ + Thu Feb 08 00:00:00 1979 +(1 row) + +SELECT random('2024-12-31'::timestamp, '2024-01-01'::timestamp); -- fail +ERROR: lower bound must be less than or equal to upper bound +SELECT random('-infinity'::timestamp, '2024-01-01'::timestamp); -- fail +ERROR: lower and upper bounds must be finite +SELECT random('2024-12-31'::timestamp, 'infinity'::timestamp); -- fail +ERROR: lower and upper bounds must be finite +-- random timestamps with timezone +SELECT random('1979-02-08 +01'::timestamptz,'2025-07-03 +02'::timestamptz) AS random_timestamptz_multiple_years; + random_timestamptz_multiple_years +------------------------------------- + Tue Jun 14 04:41:16.652896 2016 PDT +(1 row) + +SELECT random('4714-11-24 BC +00'::timestamptz,'294276-12-31 23:59:59.999999 +00'::timestamptz) AS random_timestamptz_maximum_range; + random_timestamptz_maximum_range +-------------------------------------- + Wed Mar 26 14:07:16.980265 31603 PDT +(1 row) + +SELECT random('2024-07-01 12:00:00.000001 +04'::timestamptz, '2024-07-01 12:00:00.999999 +04'::timestamptz) AS random_timestamptz_narrow_range; + random_timestamptz_narrow_range +------------------------------------- + Mon Jul 01 01:00:00.835808 2024 PDT +(1 row) + +SELECT random('1979-02-08 +05'::timestamptz,'1979-02-08 +05'::timestamptz) AS random_timestamptz_empty_range; + random_timestamptz_empty_range +-------------------------------- + Wed Feb 07 11:00:00 1979 PST +(1 row) + +SELECT random('2024-01-01 +06'::timestamptz, '2024-01-01 +07'::timestamptz); -- fail +ERROR: lower bound must be less than or equal to upper bound +SELECT random('-infinity'::timestamptz, '2024-01-01 +07'::timestamptz); -- fail +ERROR: lower and upper bounds must be finite +SELECT random('2024-01-01 +06'::timestamptz, 'infinity'::timestamptz); -- fail +ERROR: lower and upper bounds must be finite diff --git a/src/test/regress/sql/random.sql b/src/test/regress/sql/random.sql index ebfa7539ede25..890f14687ef98 100644 --- a/src/test/regress/sql/random.sql +++ b/src/test/regress/sql/random.sql @@ -277,3 +277,29 @@ SELECT random(-1e30, 1e30) FROM generate_series(1, 10); SELECT random(-0.4, 0.4) FROM generate_series(1, 10); SELECT random(0, 1 - 1e-30) FROM generate_series(1, 10); SELECT n, random(0, trim_scale(abs(1 - 10.0^(-n)))) FROM generate_series(-20, 20) n; + +-- random dates +SELECT random('1979-02-08'::date,'2025-07-03'::date) AS random_date_multiple_years; +SELECT random('4714-11-24 BC'::date,'5874897-12-31 AD'::date) AS random_date_maximum_range; +SELECT random('1979-02-08'::date,'1979-02-08'::date) AS random_date_empty_range; +SELECT random('2024-12-31'::date, '2024-01-01'::date); -- fail +SELECT random('-infinity'::date, '2024-01-01'::date); -- fail +SELECT random('2024-12-31'::date, 'infinity'::date); -- fail + +-- random timestamps +SELECT random('1979-02-08'::timestamp,'2025-07-03'::timestamp) AS random_timestamp_multiple_years; +SELECT random('4714-11-24 BC'::timestamp,'294276-12-31 23:59:59.999999'::timestamp) AS random_timestamp_maximum_range; +SELECT random('2024-07-01 12:00:00.000001'::timestamp, '2024-07-01 12:00:00.999999'::timestamp) AS random_narrow_range; +SELECT random('1979-02-08'::timestamp,'1979-02-08'::timestamp) AS random_timestamp_empty_range; +SELECT random('2024-12-31'::timestamp, '2024-01-01'::timestamp); -- fail +SELECT random('-infinity'::timestamp, '2024-01-01'::timestamp); -- fail +SELECT random('2024-12-31'::timestamp, 'infinity'::timestamp); -- fail + +-- random timestamps with timezone +SELECT random('1979-02-08 +01'::timestamptz,'2025-07-03 +02'::timestamptz) AS random_timestamptz_multiple_years; +SELECT random('4714-11-24 BC +00'::timestamptz,'294276-12-31 23:59:59.999999 +00'::timestamptz) AS random_timestamptz_maximum_range; +SELECT random('2024-07-01 12:00:00.000001 +04'::timestamptz, '2024-07-01 12:00:00.999999 +04'::timestamptz) AS random_timestamptz_narrow_range; +SELECT random('1979-02-08 +05'::timestamptz,'1979-02-08 +05'::timestamptz) AS random_timestamptz_empty_range; +SELECT random('2024-01-01 +06'::timestamptz, '2024-01-01 +07'::timestamptz); -- fail +SELECT random('-infinity'::timestamptz, '2024-01-01 +07'::timestamptz); -- fail +SELECT random('2024-01-01 +06'::timestamptz, 'infinity'::timestamptz); -- fail From 81a61fde84ffc74f7b3c7854ed4193cc4d31f78b Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 9 Sep 2025 15:33:46 +0200 Subject: [PATCH 20/96] Fix typo in comment Author: Alexandra Wang Discussion: https://www.postgresql.org/message-id/CAK98qZ0whQ%3Dc%2BJGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj%2BA%40mail.gmail.com --- src/backend/utils/adt/jsonbsubs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c index de64d49851251..e8626d3b4fc6e 100644 --- a/src/backend/utils/adt/jsonbsubs.c +++ b/src/backend/utils/adt/jsonbsubs.c @@ -51,7 +51,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, /* * Transform and convert the subscript expressions. Jsonb subscripting - * does not support slices, look only and the upper index. + * does not support slices, look only at the upper index. */ foreach(idx, indirection) { From 530cfa8eb50ca5a2151dfc50a6a5999ec8aff148 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Tue, 9 Sep 2025 14:09:36 -0500 Subject: [PATCH 21/96] test_slru: Fix LWLock tranche allocation in EXEC_BACKEND builds. Currently, test_slru's shmem_startup_hook unconditionally generates new LWLock tranche IDs. This is fine on non-EXEC_BACKEND builds, where only the postmaster executes this hook, but on EXEC_BACKEND builds, every backend executes it, too. To fix, only generate the tranche IDs in the postmaster process by checking the IsUnderPostmaster variable. This is arguably a bug fix and could be back-patched, but since the damage is limited to some extra unused tranche IDs in a test module, I'm not going to bother. Reported-by: Sami Imseih Reviewed-by: Sami Imseih Discussion: https://postgr.es/m/CAA5RZ0vaAuonaf12CeDddQJu5xKL%2B6xVyS%2B_q1%2BcH%3D33JXV82w%40mail.gmail.com --- src/test/modules/test_slru/test_slru.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c index 8c0367eeee424..e963466aef1cd 100644 --- a/src/test/modules/test_slru/test_slru.c +++ b/src/test/modules/test_slru/test_slru.c @@ -219,8 +219,8 @@ test_slru_shmem_startup(void) */ const bool long_segment_names = true; const char slru_dir_name[] = "pg_test_slru"; - int test_tranche_id; - int test_buffer_tranche_id; + int test_tranche_id = -1; + int test_buffer_tranche_id = -1; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); @@ -231,10 +231,18 @@ test_slru_shmem_startup(void) */ (void) MakePGDirectory(slru_dir_name); - /* initialize the SLRU facility */ - test_tranche_id = LWLockNewTrancheId("test_slru_tranche"); - - test_buffer_tranche_id = LWLockNewTrancheId("test_buffer_tranche"); + /* + * Initialize the SLRU facility. In EXEC_BACKEND builds, the + * shmem_startup_hook is called in the postmaster and in each backend, but + * we only need to generate the LWLock tranches once. Note that these + * tranche ID variables are not used by SimpleLruInit() when + * IsUnderPostmaster is true. + */ + if (!IsUnderPostmaster) + { + test_tranche_id = LWLockNewTrancheId("test_slru_tranche"); + test_buffer_tranche_id = LWLockNewTrancheId("test_buffer_tranche"); + } TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically; SimpleLruInit(TestSlruCtl, "TestSLRU", From d96c854dfc634212193007ca58f8978bc272d457 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Tue, 9 Sep 2025 14:35:30 -0500 Subject: [PATCH 22/96] Fix documentation for shmem_startup_hook. This section claims that each backend executes the shmem_startup_hook shortly after attaching to shared memory, which is true for EXEC_BACKEND builds, but not for others. This commit adds this important detail. Oversight in commit 964152c476. Reported-by: Sami Imseih Reviewed-by: Sami Imseih Discussion: https://postgr.es/m/CAA5RZ0vEGT1eigGbVt604LkXP6mUPMwPMxQoRCbFny44w%2B9EUQ%40mail.gmail.com Backpatch-through: 17 --- doc/src/sgml/xfunc.sgml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index da21ef5689184..04bf919b34384 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3668,11 +3668,14 @@ LWLockRelease(AddinShmemInitLock); shmem_startup_hook provides a convenient place for the initialization code, but it is not strictly required that all such code - be placed in this hook. Each backend will execute the registered - shmem_startup_hook shortly after it attaches to shared - memory. Note that add-ins should still acquire + be placed in this hook. On Windows (and anywhere else where + EXEC_BACKEND is defined), each backend executes the + registered shmem_startup_hook shortly after it + attaches to shared memory, so add-ins should still acquire AddinShmemInitLock within this hook, as shown in the - example above. + example above. On other platforms, only the postmaster process executes + the shmem_startup_hook, and each backend automatically + inherits the pointers to shared memory. From 8c8f7b199d9095dbc2e101a4614043b5ae13bde3 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 10 Sep 2025 07:23:05 +0900 Subject: [PATCH 23/96] Fix leak with SMgrRelations in startup process The startup process does not process shared invalidation messages, only sending them, and never calls AtEOXact_SMgr() which clean up any unpinned SMgrRelations. Hence, it is never able to free SMgrRelations on a periodic basis, bloating its hashtable over time. Like the checkpointer and the bgwriter, this commit takes a conservative approach by freeing periodically SMgrRelations when replaying a checkpoint record, either online or shutdown, so as the startup process has a way to perform a periodic cleanup. Issue caused by 21d9c3ee4ef7, so backpatch down to v17. Author: Jingtang Zhang Reviewed-by: Yuhang Qiu Discussion: https://postgr.es/m/28C687D4-F335-417E-B06C-6612A0BD5A10@gmail.com Backpatch-through: 17 --- src/backend/access/transam/xlog.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7ffb217915190..0baf0ac6160af 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -8385,6 +8385,14 @@ xlog_redo(XLogReaderState *record) checkPoint.ThisTimeLineID, replayTLI))); RecoveryRestartPoint(&checkPoint, record); + + /* + * After replaying a checkpoint record, free all smgr objects. + * Otherwise we would never do so for dropped relations, as the + * startup does not process shared invalidation messages or call + * AtEOXact_SMgr(). + */ + smgrdestroyall(); } else if (info == XLOG_CHECKPOINT_ONLINE) { @@ -8438,6 +8446,14 @@ xlog_redo(XLogReaderState *record) checkPoint.ThisTimeLineID, replayTLI))); RecoveryRestartPoint(&checkPoint, record); + + /* + * After replaying a checkpoint record, free all smgr objects. + * Otherwise we would never do so for dropped relations, as the + * startup does not process shared invalidation messages or call + * AtEOXact_SMgr(). + */ + smgrdestroyall(); } else if (info == XLOG_OVERWRITE_CONTRECORD) { From b1187266e077265cb061cbedd502e94179dc7b21 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 10 Sep 2025 11:20:46 +0900 Subject: [PATCH 24/96] Replace callers of dynahash.h's my_log() by equivalent in pg_bitutils.h All the calls replaced by this commit use 4-byte integers for their variables used in input of my_log2(). Hence, the limit against too-large inputs does not really apply. Thresholds are also applied, as of: - In nodeAgg.c, the number of partitions is limited by HASHAGG_MAX_PARTITIONS. - In nodeHash.c, ExecChooseHashTableSize() caps its maximum number of buckets based on HashJoinTuple and palloc() allocation limit. - In worker.c, the number of subxacts tracked by ApplySubXactData uses uint32, making pg_ceil_log2_64() safe to use directly. Several approaches have been discussed, like an integration with thresholds in pg_bitutils.h, but it was found confusing. This uses Dean's idea, which gives a simpler result than what I came up with to be able to remove dynahash.h. dynahash.h will be removed in a follow-up commit, removing some duplication with the ceil log2 routines. Reviewed-by: Peter Eisentraut Reviewed-by: Dean Rasheed Discussion: https://postgr.es/m/CAEZATCUJPQD_7sC-wErak2CQGNa6bj2hY-mr8wsBki=kX7f2_A@mail.gmail.com --- src/backend/executor/nodeAgg.c | 3 +-- src/backend/executor/nodeHash.c | 7 +++---- src/backend/replication/logical/worker.c | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 377e016d73225..a4f3d30f307cc 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -267,7 +267,6 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/datum.h" -#include "utils/dynahash.h" #include "utils/expandeddatum.h" #include "utils/injection_point.h" #include "utils/logtape.h" @@ -2115,7 +2114,7 @@ hash_choose_num_partitions(double input_groups, double hashentrysize, npartitions = (int) dpartitions; /* ceil(log2(npartitions)) */ - partition_bits = my_log2(npartitions); + partition_bits = pg_ceil_log2_32(npartitions); /* make sure that we don't exhaust the hash bits */ if (partition_bits + used_bits >= 32) diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index 8d2201ab67fa5..a3415db4e20f5 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -36,7 +36,6 @@ #include "executor/nodeHashjoin.h" #include "miscadmin.h" #include "port/pg_bitutils.h" -#include "utils/dynahash.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -340,7 +339,7 @@ MultiExecParallelHash(HashState *node) */ hashtable->curbatch = -1; hashtable->nbuckets = pstate->nbuckets; - hashtable->log2_nbuckets = my_log2(hashtable->nbuckets); + hashtable->log2_nbuckets = pg_ceil_log2_32(hashtable->nbuckets); hashtable->totalTuples = pstate->total_tuples; /* @@ -480,7 +479,7 @@ ExecHashTableCreate(HashState *state) &nbuckets, &nbatch, &num_skew_mcvs); /* nbuckets must be a power of 2 */ - log2_nbuckets = my_log2(nbuckets); + log2_nbuckets = pg_ceil_log2_32(nbuckets); Assert(nbuckets == (1 << log2_nbuckets)); /* @@ -3499,7 +3498,7 @@ ExecParallelHashTableSetCurrentBatch(HashJoinTable hashtable, int batchno) dsa_get_address(hashtable->area, hashtable->batches[batchno].shared->buckets); hashtable->nbuckets = hashtable->parallel_state->nbuckets; - hashtable->log2_nbuckets = my_log2(hashtable->nbuckets); + hashtable->log2_nbuckets = pg_ceil_log2_32(hashtable->nbuckets); hashtable->current_chunk = NULL; hashtable->current_chunk_shared = InvalidDsaPointer; hashtable->batches[batchno].at_least_one_chunk = false; diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index b3cac1023731a..ee6ac22329fdc 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -276,7 +276,6 @@ #include "storage/procarray.h" #include "tcop/tcopprot.h" #include "utils/acl.h" -#include "utils/dynahash.h" #include "utils/guc.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -5115,7 +5114,7 @@ subxact_info_read(Oid subid, TransactionId xid) len = sizeof(SubXactInfo) * subxact_data.nsubxacts; /* we keep the maximum as a power of 2 */ - subxact_data.nsubxacts_max = 1 << my_log2(subxact_data.nsubxacts); + subxact_data.nsubxacts_max = 1 << pg_ceil_log2_32(subxact_data.nsubxacts); /* * Allocate subxact information in the logical streaming context. We need From e6da68a6e1d60a037b63a9c9ed36e5ef0a996769 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 10 Sep 2025 14:11:50 +0900 Subject: [PATCH 25/96] Remove dynahash.h All the callers of my_log2() are now limited inside dynahash.c, so let's remove this header. The same capability is provided by pg_bitutils.h already. Discussion: https://postgr.es/m/CAEZATCUJPQD_7sC-wErak2CQGNa6bj2hY-mr8wsBki=kX7f2_A@mail.gmail.com --- src/backend/utils/hash/dynahash.c | 4 ++-- src/include/utils/dynahash.h | 20 -------------------- 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 src/include/utils/dynahash.h diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 1aeee5be42acd..ac94b9e93c6e3 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -102,7 +102,6 @@ #include "port/pg_bitutils.h" #include "storage/shmem.h" #include "storage/spin.h" -#include "utils/dynahash.h" #include "utils/memutils.h" @@ -281,6 +280,7 @@ static bool init_htab(HTAB *hashp, int64 nelem); pg_noreturn static void hash_corrupted(HTAB *hashp); static uint32 hash_initial_lookup(HTAB *hashp, uint32 hashvalue, HASHBUCKET **bucketptr); +static int my_log2(int64 num); static int64 next_pow2_int64(int64 num); static int next_pow2_int(int64 num); static void register_seq_scan(HTAB *hashp); @@ -1813,7 +1813,7 @@ hash_corrupted(HTAB *hashp) } /* calculate ceil(log base 2) of num */ -int +static int my_log2(int64 num) { /* diff --git a/src/include/utils/dynahash.h b/src/include/utils/dynahash.h deleted file mode 100644 index a4362d3f65e59..0000000000000 --- a/src/include/utils/dynahash.h +++ /dev/null @@ -1,20 +0,0 @@ -/*------------------------------------------------------------------------- - * - * dynahash.h - * POSTGRES dynahash.h file definitions - * - * - * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * IDENTIFICATION - * src/include/utils/dynahash.h - * - *------------------------------------------------------------------------- - */ -#ifndef DYNAHASH_H -#define DYNAHASH_H - -extern int my_log2(int64 num); - -#endif /* DYNAHASH_H */ From 33eec809402bfbf3eb0d01ad5b023d3d05fcb3bc Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 10 Sep 2025 11:49:53 +0200 Subject: [PATCH 26/96] Fix CREATE TABLE LIKE with not-valid check constraint In CREATE TABLE ... LIKE, any check constraints copied from the source table should be set to valid if they are ENFORCED (the default). Bug introduced in commit ca87c415e2f. Author: jian he Discussion: https://www.postgresql.org/message-id/CACJufxH%3D%2Bod8Wy0P4L3_GpapNwLUP3oAes5UFRJ7yTxrM_M5kg%40mail.gmail.com --- src/backend/parser/parse_utilcmd.c | 3 +-- src/test/regress/expected/create_table_like.out | 8 ++++++++ src/test/regress/sql/create_table_like.sql | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3b3..e96b38a59d503 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1461,7 +1461,6 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) char *ccname = constr->check[ccnum].ccname; char *ccbin = constr->check[ccnum].ccbin; bool ccenforced = constr->check[ccnum].ccenforced; - bool ccvalid = constr->check[ccnum].ccvalid; bool ccnoinherit = constr->check[ccnum].ccnoinherit; Node *ccbin_node; bool found_whole_row; @@ -1492,7 +1491,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) n->conname = pstrdup(ccname); n->location = -1; n->is_enforced = ccenforced; - n->initially_valid = ccvalid; + n->initially_valid = ccenforced; /* sic */ n->is_no_inherit = ccnoinherit; n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 29a779c2e9072..d3c35c148475d 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -320,6 +320,7 @@ DROP TABLE inhz; -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) ENFORCED PRIMARY KEY, b text CHECK (length(b) > 100) NOT ENFORCED); +ALTER TABLE ctlt1 ADD CONSTRAINT cc CHECK (length(b) > 100) NOT VALID; CREATE INDEX ctlt1_b_key ON ctlt1 (b); CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b)); CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1; @@ -378,6 +379,7 @@ SELECT conname, description FROM pg_description, pg_constraint c WHERE classoid CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1); NOTICE: merging column "a" with inherited definition NOTICE: merging column "b" with inherited definition +NOTICE: merging constraint "cc" with inherited definition NOTICE: merging constraint "ctlt1_a_check" with inherited definition NOTICE: merging constraint "ctlt1_b_check" with inherited definition \d+ ctlt1_inh @@ -387,6 +389,7 @@ NOTICE: merging constraint "ctlt1_b_check" with inherited definition a | text | | not null | | main | | A b | text | | | | extended | | B Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED Not-null constraints: @@ -409,6 +412,7 @@ NOTICE: merging multiple inherited definitions of column "a" b | text | | | | extended | | c | text | | | | external | | Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED "ctlt3_a_check" CHECK (length(a) < 5) @@ -430,6 +434,7 @@ NOTICE: merging column "a" with inherited definition Indexes: "ctlt13_like_expr_idx" btree ((a || c)) Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED "ctlt3_a_check" CHECK (length(a) < 5) @@ -456,6 +461,7 @@ Indexes: "ctlt_all_b_idx" btree (b) "ctlt_all_expr_idx" btree ((a || b)) Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED Statistics objects: @@ -499,6 +505,7 @@ Indexes: "pg_attrdef_b_idx" btree (b) "pg_attrdef_expr_idx" btree ((a || b)) Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED Statistics objects: @@ -524,6 +531,7 @@ Indexes: "ctlt1_b_idx" btree (b) "ctlt1_expr_idx" btree ((a || b)) Check constraints: + "cc" CHECK (length(b) > 100) "ctlt1_a_check" CHECK (length(a) > 2) "ctlt1_b_check" CHECK (length(b) > 100) NOT ENFORCED Statistics objects: diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index bf8702116a74b..93389b57dbf95 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -130,6 +130,7 @@ DROP TABLE inhz; -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) ENFORCED PRIMARY KEY, b text CHECK (length(b) > 100) NOT ENFORCED); +ALTER TABLE ctlt1 ADD CONSTRAINT cc CHECK (length(b) > 100) NOT VALID; CREATE INDEX ctlt1_b_key ON ctlt1 (b); CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b)); CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1; From 9016fa7e3bcde8ae4c3d63c707143af147486a10 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 10 Sep 2025 11:21:12 -0500 Subject: [PATCH 27/96] meson: Build numeric.c with -ftree-vectorize. autoconf builds have compiled this file with -ftree-vectorize since commit 8870917623, but meson builds seem to have missed the memo. Reviewed-by: Jeff Davis Discussion: https://postgr.es/m/aL85CeasM51-0D1h%40nathan Backpatch-through: 16 --- src/backend/utils/adt/meson.build | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/meson.build b/src/backend/utils/adt/meson.build index dac372c3bea3b..12fa0c209127c 100644 --- a/src/backend/utils/adt/meson.build +++ b/src/backend/utils/adt/meson.build @@ -1,5 +1,15 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group +# Some code in numeric.c benefits from auto-vectorization +numeric_backend_lib = static_library('numeric_backend_lib', + 'numeric.c', + dependencies: backend_build_deps, + kwargs: internal_lib_args, + c_args: vectorize_cflags, +) + +backend_link_with += numeric_backend_lib + backend_sources += files( 'acl.c', 'amutils.c', @@ -61,7 +71,6 @@ backend_sources += files( 'network_gist.c', 'network_selfuncs.c', 'network_spgist.c', - 'numeric.c', 'numutils.c', 'oid.c', 'oracle_compat.c', From abdeacdb0920d94dec7500d09f6f29fbb2f6310d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 10 Sep 2025 16:05:03 -0400 Subject: [PATCH 28/96] Fix memory leakage in nodeSubplan.c. If the hash functions used for hashing tuples leaked any memory, we failed to clean that up, resulting in query-lifespan memory leakage in queries using hashed subplans. One way that could happen is if the values being hashed require de-toasting, since most of our hash functions don't trouble to clean up de-toasted inputs. Prior to commit bf6c614a2, this leakage was largely masked because TupleHashTableMatch would reset hashtable->tempcxt (via execTuplesMatch). But it doesn't do that anymore, and that's not really the right place for this anyway: doing it there could reset the tempcxt many times per hash lookup, or not at all. Instead put reset calls into ExecHashSubPlan and buildSubPlanHash. Along the way to that, rearrange ExecHashSubPlan so that there's just one place to call MemoryContextReset instead of several. This amounts to accepting the de-facto API spec that the caller of the TupleHashTable routines is responsible for resetting the tempcxt adequately often. Although the other callers seem to get this right, it was not documented anywhere, so add a comment about it. Bug: #19040 Reported-by: Haiyang Li Author: Haiyang Li Reviewed-by: Fei Changhong Reviewed-by: Tom Lane Discussion: https://postgr.es/m/19040-c9b6073ef814f48c@postgresql.org Backpatch-through: 13 --- src/backend/executor/execGrouping.c | 6 +++ src/backend/executor/nodeSubplan.c | 70 +++++++++++------------------ 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c index b540074935386..75087204f0c69 100644 --- a/src/backend/executor/execGrouping.c +++ b/src/backend/executor/execGrouping.c @@ -156,6 +156,12 @@ execTuplesHashPrepare(int numCols, * * Note that the keyColIdx, hashfunctions, and collations arrays must be * allocated in storage that will live as long as the hashtable does. + * + * LookupTupleHashEntry, FindTupleHashEntry, and related functions may leak + * memory in the tempcxt. It is caller's responsibility to reset that context + * reasonably often, typically once per tuple. (We do it that way, rather + * than managing an extra context within the hashtable, because in many cases + * the caller can specify a tempcxt that it needs to reset per-tuple anyway.) */ TupleHashTable BuildTupleHashTable(PlanState *parent, diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index f7f6fc2da0b95..8e55dcc159b0b 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -102,6 +102,7 @@ ExecHashSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { + bool result = false; SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; TupleTableSlot *slot; @@ -132,14 +133,6 @@ ExecHashSubPlan(SubPlanState *node, node->projLeft->pi_exprContext = econtext; slot = ExecProject(node->projLeft); - /* - * Note: because we are typically called in a per-tuple context, we have - * to explicitly clear the projected tuple before returning. Otherwise, - * we'll have a double-free situation: the per-tuple context will probably - * be reset before we're called again, and then the tuple slot will think - * it still needs to free the tuple. - */ - /* * If the LHS is all non-null, probe for an exact match in the main hash * table. If we find one, the result is TRUE. Otherwise, scan the @@ -161,19 +154,10 @@ ExecHashSubPlan(SubPlanState *node, slot, node->cur_eq_comp, node->lhs_hash_expr) != NULL) - { - ExecClearTuple(slot); - return BoolGetDatum(true); - } - if (node->havenullrows && - findPartialMatch(node->hashnulls, slot, node->cur_eq_funcs)) - { - ExecClearTuple(slot); + result = true; + else if (node->havenullrows && + findPartialMatch(node->hashnulls, slot, node->cur_eq_funcs)) *isNull = true; - return BoolGetDatum(false); - } - ExecClearTuple(slot); - return BoolGetDatum(false); } /* @@ -186,34 +170,31 @@ ExecHashSubPlan(SubPlanState *node, * aren't provably unequal to the LHS; if so, the result is UNKNOWN. * Otherwise, the result is FALSE. */ - if (node->hashnulls == NULL) - { - ExecClearTuple(slot); - return BoolGetDatum(false); - } - if (slotAllNulls(slot)) - { - ExecClearTuple(slot); + else if (node->hashnulls == NULL) + /* just return FALSE */ ; + else if (slotAllNulls(slot)) *isNull = true; - return BoolGetDatum(false); - } /* Scan partly-null table first, since more likely to get a match */ - if (node->havenullrows && - findPartialMatch(node->hashnulls, slot, node->cur_eq_funcs)) - { - ExecClearTuple(slot); + else if (node->havenullrows && + findPartialMatch(node->hashnulls, slot, node->cur_eq_funcs)) *isNull = true; - return BoolGetDatum(false); - } - if (node->havehashrows && - findPartialMatch(node->hashtable, slot, node->cur_eq_funcs)) - { - ExecClearTuple(slot); + else if (node->havehashrows && + findPartialMatch(node->hashtable, slot, node->cur_eq_funcs)) *isNull = true; - return BoolGetDatum(false); - } + + /* + * Note: because we are typically called in a per-tuple context, we have + * to explicitly clear the projected tuple before returning. Otherwise, + * we'll have a double-free situation: the per-tuple context will probably + * be reset before we're called again, and then the tuple slot will think + * it still needs to free the tuple. + */ ExecClearTuple(slot); - return BoolGetDatum(false); + + /* Also must reset the hashtempcxt after each hashtable lookup. */ + MemoryContextReset(node->hashtempcxt); + + return BoolGetDatum(result); } /* @@ -642,6 +623,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) * during ExecProject. */ ResetExprContext(innerecontext); + + /* Also must reset the hashtempcxt after each hashtable lookup. */ + MemoryContextReset(node->hashtempcxt); } /* From bdc6cfcd12f5c95799328e05aa4bfa75cfe3e79f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 10 Sep 2025 16:15:08 -0400 Subject: [PATCH 29/96] Eliminate duplicative hashtempcxt in nodeSubplan.c. Instead of building a separate memory context that's used just for running hash functions, make the hash functions run in the per-tuple context of the node's innerecontext. This saves a little space at runtime, and it avoids needing to reset two contexts instead of one inside buildSubPlanHash's main loop. This largely reverts commit 133924e13. That's safe to do now because bf6c614a2 decoupled the evaluation context used by TupleHashTableMatch from that used for hash function evaluation, so that there's no longer a risk of resetting the innerecontext too soon. Per discussion of bug #19040, although this is not directly a fix for that. Author: Tom Lane Reviewed-by: Haiyang Li Reviewed-by: Fei Changhong Discussion: https://postgr.es/m/19040-c9b6073ef814f48c@postgresql.org --- src/backend/executor/nodeSubplan.c | 19 +++++-------------- src/include/nodes/execnodes.h | 1 - 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 8e55dcc159b0b..53fb56f7388e8 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -191,8 +191,8 @@ ExecHashSubPlan(SubPlanState *node, */ ExecClearTuple(slot); - /* Also must reset the hashtempcxt after each hashtable lookup. */ - MemoryContextReset(node->hashtempcxt); + /* Also must reset the innerecontext after each hashtable lookup. */ + ResetExprContext(node->innerecontext); return BoolGetDatum(result); } @@ -529,7 +529,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) 0, node->planstate->state->es_query_cxt, node->hashtablecxt, - node->hashtempcxt, + innerecontext->ecxt_per_tuple_memory, false); if (!subplan->unknownEqFalse) @@ -558,7 +558,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) 0, node->planstate->state->es_query_cxt, node->hashtablecxt, - node->hashtempcxt, + innerecontext->ecxt_per_tuple_memory, false); } else @@ -620,12 +620,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) /* * Reset innerecontext after each inner tuple to free any memory used - * during ExecProject. + * during ExecProject and hashtable lookup. */ ResetExprContext(innerecontext); - - /* Also must reset the hashtempcxt after each hashtable lookup. */ - MemoryContextReset(node->hashtempcxt); } /* @@ -842,7 +839,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) sstate->hashtable = NULL; sstate->hashnulls = NULL; sstate->hashtablecxt = NULL; - sstate->hashtempcxt = NULL; sstate->innerecontext = NULL; sstate->keyColIdx = NULL; sstate->tab_eq_funcoids = NULL; @@ -898,11 +894,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) AllocSetContextCreate(CurrentMemoryContext, "Subplan HashTable Context", ALLOCSET_DEFAULT_SIZES); - /* and a small one for the hash tables to use as temp storage */ - sstate->hashtempcxt = - AllocSetContextCreate(CurrentMemoryContext, - "Subplan HashTable Temp Context", - ALLOCSET_SMALL_SIZES); /* and a short-lived exprcontext for function evaluation */ sstate->innerecontext = CreateExprContext(estate); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index de782014b2d41..71857feae4823 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1020,7 +1020,6 @@ typedef struct SubPlanState bool havehashrows; /* true if hashtable is not empty */ bool havenullrows; /* true if hashnulls is not empty */ MemoryContext hashtablecxt; /* memory context containing hash tables */ - MemoryContext hashtempcxt; /* temp memory context for hash tables */ ExprContext *innerecontext; /* econtext for computing inner tuples */ int numCols; /* number of columns being hashed */ /* each of the remaining fields is an array of length numCols: */ From 09036dc71c682b0bf7234ed39c1429ed99fbe442 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 10 Sep 2025 17:51:24 -0400 Subject: [PATCH 30/96] Avoid faulty alignment of Datums in build_sorted_items(). If sizeof(Pointer) is 4 then sizeof(SortItem) will be 12, so that if data->numrows is odd then we placed the values array at a location that's not a multiple of 8. That was fine when sizeof(Datum) was also 4, but in the wake of commit 2a600a93c it makes some alignment-picky machines unhappy. (You need a 32-bit machine that nonetheless expects 8-byte alignment of 8-byte quantities, which is an odd-seeming combination but it does exist outside the Intel universe.) To fix, MAXALIGN the space allocated to the SortItem array. In passing, let's make the "len" variable be Size not int, just for paranoia's sake. This code was arguably not too safe even before 2a600a93c, but at present I don't see a strong argument for back-patching. Reported-by: Tomas Vondra Author: Tom Lane Discussion: https://postgr.es/m/87036018-8d70-40ad-a0ac-192b07bd7b04@vondra.me --- src/backend/statistics/extended_stats.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index af0b99243c614..3c3d2d315c6f4 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -986,10 +986,9 @@ build_sorted_items(StatsBuildData *data, int *nitems, { int i, j, - len, nrows; int nvalues = data->numrows * numattrs; - + Size len; SortItem *items; Datum *values; bool *isnull; @@ -997,14 +996,16 @@ build_sorted_items(StatsBuildData *data, int *nitems, int *typlen; /* Compute the total amount of memory we need (both items and values). */ - len = data->numrows * sizeof(SortItem) + nvalues * (sizeof(Datum) + sizeof(bool)); + len = MAXALIGN(data->numrows * sizeof(SortItem)) + + nvalues * (sizeof(Datum) + sizeof(bool)); /* Allocate the memory and split it into the pieces. */ ptr = palloc0(len); /* items to sort */ items = (SortItem *) ptr; - ptr += data->numrows * sizeof(SortItem); + /* MAXALIGN ensures that the following Datums are suitably aligned */ + ptr += MAXALIGN(data->numrows * sizeof(SortItem)); /* values and null flags */ values = (Datum *) ptr; From c88ce73eda2e0a818d730c5b72475ef99cc9c4cf Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 11 Sep 2025 10:15:33 +0900 Subject: [PATCH 31/96] Fix incorrect file reference in guc.h GucSource_Names was documented as being in guc.c, but since 0a20ff54f5e6 it is located in guc_tables.c. The reference to the location of GucSource_Names is important, as GucSource needs to be kept in sync with GucSource_Names. Author: David G. Johnston Discussion: https://postgr.es/m/CAKFQuwYPgAHWPYjPzK7iXzhSZ6MKR8w20_Nz7ZXpOvx=kZbs7A@mail.gmail.com Backpatch-through: 16 --- src/include/utils/guc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 756e80a2c2fcc..f21ec37da8933 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -106,7 +106,7 @@ typedef enum * will show as "default" in pg_settings. If there is a specific reason not * to want that, use source == PGC_S_OVERRIDE. * - * NB: see GucSource_Names in guc.c if you change this. + * NB: see GucSource_Names in guc_tables.c if you change this. */ typedef enum { From 26eadf4d2b14a8a8d110b214dbb7ef952fbf8e93 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 11 Sep 2025 17:17:04 +0900 Subject: [PATCH 32/96] Fix description of WAL record blocks in hash_xlog.h hash_xlog.h included descriptions for the blocks used in WAL records that were was not completely consistent with how the records are generated, with one block missing for SQUEEZE_PAGE, and inconsistent descriptions used for block 0 in VACUUM_ONE_PAGE and MOVE_PAGE_CONTENTS. This information was incorrect since c11453ce0aea, cross-checking the logic for the record generation. Author: Kirill Reshke Reviewed-by: Andrey Borodin Discussion: https://postgr.es/m/CALdSSPj1j=a1d1hVA3oabRFz0hSU3KKrYtZPijw4UPUM7LY9zw@mail.gmail.com Backpatch-through: 13 --- src/include/access/hash_xlog.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/include/access/hash_xlog.h b/src/include/access/hash_xlog.h index 6fe97de4d66f1..5d4671dc4c128 100644 --- a/src/include/access/hash_xlog.h +++ b/src/include/access/hash_xlog.h @@ -129,7 +129,7 @@ typedef struct xl_hash_split_complete * * This data record is used for XLOG_HASH_MOVE_PAGE_CONTENTS * - * Backup Blk 0: bucket page + * Backup Blk 0: primary bucket page * Backup Blk 1: page containing moved tuples * Backup Blk 2: page from which tuples will be removed */ @@ -149,12 +149,13 @@ typedef struct xl_hash_move_page_contents * * This data record is used for XLOG_HASH_SQUEEZE_PAGE * - * Backup Blk 0: page containing tuples moved from freed overflow page - * Backup Blk 1: freed overflow page - * Backup Blk 2: page previous to the freed overflow page - * Backup Blk 3: page next to the freed overflow page - * Backup Blk 4: bitmap page containing info of freed overflow page - * Backup Blk 5: meta page + * Backup Blk 0: primary bucket page + * Backup Blk 1: page containing tuples moved from freed overflow page + * Backup Blk 2: freed overflow page + * Backup Blk 3: page previous to the freed overflow page + * Backup Blk 4: page next to the freed overflow page + * Backup Blk 5: bitmap page containing info of freed overflow page + * Backup Blk 6: meta page */ typedef struct xl_hash_squeeze_page { @@ -245,7 +246,7 @@ typedef struct xl_hash_init_bitmap_page * * This data record is used for XLOG_HASH_VACUUM_ONE_PAGE * - * Backup Blk 0: bucket page + * Backup Blk 0: primary bucket page * Backup Blk 1: meta page */ typedef struct xl_hash_vacuum_one_page From 9c24111c4dad850bc2625c2113bb3d2dfd592efc Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 11 Sep 2025 09:25:47 +0100 Subject: [PATCH 33/96] doc: Improve description of new random(min, max) functions. Mention that the new variants of random(min, max) are affected by setseed(), like the original functions. Reported-by: Marcos Pegoraro Discussion: https://postgr.es/m/CAB-JLwb1=drA3Le6uZXDBi_tCpeS1qm6XQU7dKwac_x91Z4qDg@mail.gmail.com --- doc/src/sgml/func/func-datetime.sgml | 6 ++++++ doc/src/sgml/func/func-math.sgml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/func/func-datetime.sgml b/doc/src/sgml/func/func-datetime.sgml index 98dd60aa9a7ec..a25da4b5175ca 100644 --- a/doc/src/sgml/func/func-datetime.sgml +++ b/doc/src/sgml/func/func-datetime.sgml @@ -948,6 +948,12 @@ Returns a random value in the range min <= x <= max. + + Note that these functions use the same pseudo-random number generator + as the functions listed in , + and respond in the same way to calling + setseed(). + random('1979-02-08'::date,'2025-07-03'::date) 1983-04-21 diff --git a/doc/src/sgml/func/func-math.sgml b/doc/src/sgml/func/func-math.sgml index fd821c0e70677..9dcf97e7c9e06 100644 --- a/doc/src/sgml/func/func-math.sgml +++ b/doc/src/sgml/func/func-math.sgml @@ -1130,7 +1130,7 @@ - + setseed setseed ( double precision ) From 2bbbb2eca9303df590cc79be74b13cad259124a5 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 11 Sep 2025 09:48:12 +0100 Subject: [PATCH 34/96] doc: Fix indentation in func-datetime.sgml. Incorrect indentation introduced by commit faf071b5538. --- doc/src/sgml/func/func-datetime.sgml | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/doc/src/sgml/func/func-datetime.sgml b/doc/src/sgml/func/func-datetime.sgml index a25da4b5175ca..8cd7150b0d313 100644 --- a/doc/src/sgml/func/func-datetime.sgml +++ b/doc/src/sgml/func/func-datetime.sgml @@ -935,34 +935,34 @@ random ( min date, max date ) date - - - random ( min timestamp, max timestamp ) - timestamp - - - random ( min timestamptz, max timestamptz ) - timestamptz - - - Returns a random value in the range - min <= x <= max. - - - Note that these functions use the same pseudo-random number generator - as the functions listed in , - and respond in the same way to calling - setseed(). - - - random('1979-02-08'::date,'2025-07-03'::date) - 1983-04-21 - - - random('2000-01-01'::timestamptz, now()) - 2015-09-27 09:11:33.732707+00 - - + + + random ( min timestamp, max timestamp ) + timestamp + + + random ( min timestamptz, max timestamptz ) + timestamptz + + + Returns a random value in the range + min <= x <= max. + + + Note that these functions use the same pseudo-random number generator + as the functions listed in , + and respond in the same way to calling + setseed(). + + + random('1979-02-08'::date,'2025-07-03'::date) + 1983-04-21 + + + random('2000-01-01'::timestamptz, now()) + 2015-09-27 09:11:33.732707+00 + + From 01d793698f5921547a7b5e1e003722c17f552574 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 11 Sep 2025 09:33:48 +0000 Subject: [PATCH 35/96] Fix intermittent test failure introduced in 6456c6e2c4. The test assumes that a backend will execute COMMIT PREPARED on the publisher and hit the injection point commit-after-delay-checkpoint within the commit critical section. This should cause the apply worker on the subscriber to wait for the transaction to complete. However, the test does not guarantee that the injection point is actually triggered, creating a race condition where the apply worker may proceed prematurely during COMMIT PREPARED. This commit resolves the issue by explicitly waiting for the injection point to be hit before continuing with the test, ensuring consistent and reliable behavior. Author: Zhijie Hou Reviewed-by: shveta malik Discussion: https://postgr.es/m/TY4PR01MB1690751D1CA8C128B0770EC6F9409A@TY4PR01MB16907.jpnprd01.prod.outlook.com --- src/test/subscription/t/035_conflicts.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index db0d5b464e825..880551fc69d74 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -475,6 +475,9 @@ } ); + # Wait until the backend enters the injection point + $node_B->wait_for_event('client backend', 'commit-after-delay-checkpoint'); + # Confirm the update is suspended $result = $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1'); From 368c38dd47c209fa95d2df855d62bcde386f3037 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 11 Sep 2025 11:55:29 +0200 Subject: [PATCH 36/96] Remove stray semicolon at global scope The Sun Studio compiler complains about an empty declaration here. Note for future historians: This does not mean that this compiler is still of current interest for anyone using PostgreSQL. But we can let this small fix be its parting gift. Reviewed-by: Andres Freund Reviewed-by: Tom Lane Discussion: https://www.postgresql.org/message-id/flat/a0f817ee-fb86-483a-8a14-b6f7f5991b6e%40eisentraut.org --- src/backend/replication/logical/slotsync.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index 9d0072a49ed6d..8c061d55bdb51 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -1337,7 +1337,7 @@ reset_syncing_flag() SpinLockRelease(&SlotSyncCtx->mutex); syncing_slots = false; -}; +} /* * The main loop of our worker process. From 4fbe0151455fefbef7abc9d507adb04c978beb0d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 11 Sep 2025 11:55:29 +0200 Subject: [PATCH 37/96] Remove checks for no longer supported GCC versions Since commit f5e0186f865 (Raise C requirement to C11), we effectively require at least GCC version 4.7, so checks for older versions can be removed. Reviewed-by: Andres Freund Reviewed-by: Tom Lane Discussion: https://www.postgresql.org/message-id/flat/a0f817ee-fb86-483a-8a14-b6f7f5991b6e%40eisentraut.org --- src/include/c.h | 8 ++++---- src/include/port/atomics/generic-gcc.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd75..b580cfa7d3178 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -259,8 +259,8 @@ * choose not to. But, if possible, don't force inlining in unoptimized * debug builds. */ -#if (defined(__GNUC__) && __GNUC__ > 3 && defined(__OPTIMIZE__)) || defined(__SUNPRO_C) -/* GCC > 3 and Sunpro support always_inline via __attribute__ */ +#if (defined(__GNUC__) && defined(__OPTIMIZE__)) || defined(__SUNPRO_C) +/* GCC and Sunpro support always_inline via __attribute__ */ #define pg_attribute_always_inline __attribute__((always_inline)) inline #elif defined(_MSC_VER) /* MSVC has a special keyword for this */ @@ -277,7 +277,7 @@ * above, this should be placed before the function's return type and name. */ /* GCC and Sunpro support noinline via __attribute__ */ -#if (defined(__GNUC__) && __GNUC__ > 2) || defined(__SUNPRO_C) +#if defined(__GNUC__) || defined(__SUNPRO_C) #define pg_noinline __attribute__((noinline)) /* msvc via declspec */ #elif defined(_MSC_VER) @@ -369,7 +369,7 @@ * These should only be used sparingly, in very hot code paths. It's very easy * to mis-estimate likelihoods. */ -#if __GNUC__ >= 3 +#ifdef __GNUC__ #define likely(x) __builtin_expect((x) != 0, 1) #define unlikely(x) __builtin_expect((x) != 0, 0) #else diff --git a/src/include/port/atomics/generic-gcc.h b/src/include/port/atomics/generic-gcc.h index d8f04c89ccac2..e7dfad4f0d5eb 100644 --- a/src/include/port/atomics/generic-gcc.h +++ b/src/include/port/atomics/generic-gcc.h @@ -30,14 +30,14 @@ #define pg_compiler_barrier_impl() __asm__ __volatile__("" ::: "memory") /* - * If we're on GCC 4.1.0 or higher, we should be able to get a memory barrier + * If we're on GCC, we should be able to get a memory barrier * out of this compiler built-in. But we prefer to rely on platform specific * definitions where possible, and use this only as a fallback. */ #if !defined(pg_memory_barrier_impl) # if defined(HAVE_GCC__ATOMIC_INT32_CAS) # define pg_memory_barrier_impl() __atomic_thread_fence(__ATOMIC_SEQ_CST) -# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +# elif defined(__GNUC__) # define pg_memory_barrier_impl() __sync_synchronize() # endif #endif /* !defined(pg_memory_barrier_impl) */ From a2b4102a21ad730ce46b059acf49d72151e979f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 11 Sep 2025 18:11:46 +0200 Subject: [PATCH 38/96] Fill testing gap for possible referential integrity violation This commit adds a missing isolation test for (non-PERIOD) foreign keys. With REPEATABLE READ, one transaction can insert a referencing row while another deletes the referenced row, and both see a valid state. But after they have committed, the table violates referential integrity. If the INSERT precedes the DELETE, we use a crosscheck snapshot to see the just-added row, so that the DELETE can raise a foreign key error. You can see the table violate referential integrity if you change ri_restrict to pass false for detectNewRows to ri_PerformCheck. A crosscheck snapshot is not needed when the DELETE comes first, because the INSERT's trigger takes a FOR KEY SHARE lock that sees the row now marked for deletion, waits for that transaction to commit, and raises a serialization error. I (Paul) added a test for that too though. We already have a similar test (in ri-triggers.spec) for SERIALIZABLE snapshot isolation showing that you can implement foreign keys with just pl/pgSQL, but that test does nothing to validate ri_triggers.c. We also have tests (in fk-snapshot.spec) for other concurrency scenarios, but not this one: we test concurrently deleting both the referencing and referenced row, when the constraint activates a cascade/set null action. But those tests don't exercise ri_restrict, and the consequence of omitting a crosscheck comparison is different: a serialization failure, not a referential integrity violation. Author: Paul Jungwirth Reviewed-by: Rustam ALLAKOV Reviewed-by: Dean Rasheed Reviewed-by: Robert Haas Discussion: https://postgr.es/m/CA+renyUp=xja80rBaB6NpY3RRdi750y046x28bo_xg29zKY72Q@mail.gmail.com --- src/test/isolation/expected/fk-snapshot-2.out | 61 +++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + src/test/isolation/specs/fk-snapshot-2.spec | 50 +++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/test/isolation/expected/fk-snapshot-2.out create mode 100644 src/test/isolation/specs/fk-snapshot-2.spec diff --git a/src/test/isolation/expected/fk-snapshot-2.out b/src/test/isolation/expected/fk-snapshot-2.out new file mode 100644 index 0000000000000..0a4c9646fca4e --- /dev/null +++ b/src/test/isolation/expected/fk-snapshot-2.out @@ -0,0 +1,61 @@ +Parsed test spec with 2 sessions + +starting permutation: s1rr s2rr s2ins s1del s2c s1c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rr s2rr s1del s2ins s1c s2c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1rc s2rc s2ins s1del s2c s1c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rc s2rc s1del s2ins s1c s2c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: insert or update on table "child" violates foreign key constraint "child_parent_id_fkey" +step s2c: COMMIT; + +starting permutation: s1ser s2ser s2ins s1del s2c s1c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1ser s2ser s1del s2ins s1c s2c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1del: DELETE FROM parent WHERE parent_id = 1; +step s2ins: INSERT INTO child VALUES (1, 1); +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 9f1e997d81b00..130525dfd3d68 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -36,6 +36,7 @@ test: fk-deadlock2 test: fk-partitioned-1 test: fk-partitioned-2 test: fk-snapshot +test: fk-snapshot-2 test: subxid-overflow test: eval-plan-qual test: eval-plan-qual-trigger diff --git a/src/test/isolation/specs/fk-snapshot-2.spec b/src/test/isolation/specs/fk-snapshot-2.spec new file mode 100644 index 0000000000000..94cd151aab9d3 --- /dev/null +++ b/src/test/isolation/specs/fk-snapshot-2.spec @@ -0,0 +1,50 @@ +# RI Trigger test +# +# Test C-based referential integrity enforcement. +# Under REPEATABLE READ we need some snapshot trickery in C, +# or we would permit things that violate referential integrity. + +setup +{ + CREATE TABLE parent (parent_id SERIAL NOT NULL PRIMARY KEY); + CREATE TABLE child ( + child_id SERIAL NOT NULL PRIMARY KEY, + parent_id INTEGER REFERENCES parent); + INSERT INTO parent VALUES(1); +} + +teardown { DROP TABLE parent, child; } + +session s1 +step s1rc { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s1rr { BEGIN ISOLATION LEVEL REPEATABLE READ; } +step s1ser { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s1del { DELETE FROM parent WHERE parent_id = 1; } +step s1c { COMMIT; } + +session s2 +step s2rc { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s2rr { BEGIN ISOLATION LEVEL REPEATABLE READ; } +step s2ser { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s2ins { INSERT INTO child VALUES (1, 1); } +step s2c { COMMIT; } + +# Violates referential integrity unless we use a crosscheck snapshot, +# which is up-to-date compared with the transaction's snapshot. +permutation s1rr s2rr s2ins s1del s2c s1c + +# Raises a can't-serialize exception +# when the INSERT trigger does SELECT FOR KEY SHARE: +permutation s1rr s2rr s1del s2ins s1c s2c + +# Test the same scenarios in READ COMMITTED: +# A crosscheck snapshot is not required here. +permutation s1rc s2rc s2ins s1del s2c s1c +permutation s1rc s2rc s1del s2ins s1c s2c + +# Test the same scenarios in SERIALIZABLE: +# We should report the FK violation: +permutation s1ser s2ser s2ins s1del s2c s1c +# We raise a concurrent update error +# which is good enough: +permutation s1ser s2ser s1del s2ins s1c s2c From e8cec3d1791c140398454aa561cf51659dd8243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 11 Sep 2025 18:13:09 +0200 Subject: [PATCH 39/96] Add test for temporal referential integrity This commit adds an isolation test showing that temporal foreign keys do not permit referential integrity violations under concurrency, like fk-snapshot-2. You can show that the test fails by passing false for detectNewRows to ri_PerformCheck in ri_restrict. Author: Paul Jungwirth Reviewed-by: Rustam ALLAKOV Reviewed-by: Dean Rasheed Reviewed-by: Robert Haas Discussion: https://postgr.es/m/CA+renyUp=xja80rBaB6NpY3RRdi750y046x28bo_xg29zKY72Q@mail.gmail.com --- src/test/isolation/expected/fk-snapshot-3.out | 213 ++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + src/test/isolation/specs/fk-snapshot-3.spec | 82 +++++++ 3 files changed, 296 insertions(+) create mode 100644 src/test/isolation/expected/fk-snapshot-3.out create mode 100644 src/test/isolation/specs/fk-snapshot-3.spec diff --git a/src/test/isolation/expected/fk-snapshot-3.out b/src/test/isolation/expected/fk-snapshot-3.out new file mode 100644 index 0000000000000..f98cb72fdac30 --- /dev/null +++ b/src/test/isolation/expected/fk-snapshot-3.out @@ -0,0 +1,213 @@ +Parsed test spec with 2 sessions + +starting permutation: s1rr s2rr s2ins s1del s2c s1c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rr s2rr s1del s2ins s1c s2c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1rc s2rc s2ins s1del s2c s1c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rc s2rc s1del s2ins s1c s2c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: insert or update on table "child" violates foreign key constraint "child_parent_id_valid_at_fkey" +step s2c: COMMIT; + +starting permutation: s1ser s2ser s2ins s1del s2c s1c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1del: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1ser s2ser s1del s2ins s1c s2c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1del: DELETE FROM parent WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1rc s2rc s2ins s1upok s2c s1c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upok: <... completed> +step s1c: COMMIT; + +starting permutation: s1rc s2rc s1upok s2ins s1c s2c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +step s2c: COMMIT; + +starting permutation: s1rr s2rr s2ins s1upok s2c s1c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upok: <... completed> +step s1c: COMMIT; + +starting permutation: s1rr s2rr s1upok s2ins s1c s2c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1ser s2ser s2ins s1upok s2c s1c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upok: <... completed> +step s1c: COMMIT; + +starting permutation: s1ser s2ser s1upok s2ins s1c s2c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1upok: UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1rc s2rc s2ins s1upbad s2c s1c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upbad: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rc s2rc s1upbad s2ins s1c s2c +step s1rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2rc: BEGIN ISOLATION LEVEL READ COMMITTED; +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: insert or update on table "child" violates foreign key constraint "child_parent_id_valid_at_fkey" +step s2c: COMMIT; + +starting permutation: s1rr s2rr s2ins s1upbad s2c s1c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upbad: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1rr s2rr s1upbad s2ins s1c s2c +step s1rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2rr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; + +starting permutation: s1ser s2ser s2ins s1upbad s2c s1c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2c: COMMIT; +step s1upbad: <... completed> +ERROR: update or delete on table "parent" violates foreign key constraint "child_parent_id_valid_at_fkey" on table "child" +step s1c: COMMIT; + +starting permutation: s1ser s2ser s1upbad s2ins s1c s2c +step s1ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2ser: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1upbad: UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; +step s2ins: + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); + +step s1c: COMMIT; +step s2ins: <... completed> +ERROR: could not serialize access due to concurrent update +step s2c: COMMIT; diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 130525dfd3d68..5afae33d37036 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -37,6 +37,7 @@ test: fk-partitioned-1 test: fk-partitioned-2 test: fk-snapshot test: fk-snapshot-2 +test: fk-snapshot-3 test: subxid-overflow test: eval-plan-qual test: eval-plan-qual-trigger diff --git a/src/test/isolation/specs/fk-snapshot-3.spec b/src/test/isolation/specs/fk-snapshot-3.spec new file mode 100644 index 0000000000000..90075024f5cc0 --- /dev/null +++ b/src/test/isolation/specs/fk-snapshot-3.spec @@ -0,0 +1,82 @@ +# RI Trigger test +# +# Test C-based temporal referential integrity enforcement. +# Under REPEATABLE READ we need some snapshot trickery in C, +# or we would permit things that violate referential integrity. + +setup +{ + CREATE TABLE parent ( + id int4range NOT NULL, + valid_at daterange NOT NULL, + PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)); + CREATE TABLE child ( + id int4range NOT NULL, + valid_at daterange NOT NULL, + parent_id int4range, + FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES parent); + INSERT INTO parent VALUES ('[1,2)', '[2020-01-01,2030-01-01)'); +} + +teardown { DROP TABLE parent, child; } + +session s1 +step s1rc { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s1rr { BEGIN ISOLATION LEVEL REPEATABLE READ; } +step s1ser { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s1del { DELETE FROM parent WHERE id = '[1,2)'; } +step s1upok { UPDATE parent SET valid_at = '[2020-01-01,2026-01-01)' WHERE id = '[1,2)'; } +step s1upbad { UPDATE parent SET valid_at = '[2020-01-01,2024-01-01)' WHERE id = '[1,2)'; } +step s1c { COMMIT; } + +session s2 +step s2rc { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s2rr { BEGIN ISOLATION LEVEL REPEATABLE READ; } +step s2ser { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s2ins { + INSERT INTO child VALUES ('[1,2)', '[2020-01-01,2025-01-01)', '[1,2)'); +} +step s2c { COMMIT; } + +# Violates referential integrity unless we use an up-to-date crosscheck snapshot: +permutation s1rr s2rr s2ins s1del s2c s1c + +# Raises a can't-serialize exception +# when the INSERT trigger does SELECT FOR KEY SHARE: +permutation s1rr s2rr s1del s2ins s1c s2c + +# Test the same scenarios in READ COMMITTED: +# A crosscheck snapshot is not required here. +permutation s1rc s2rc s2ins s1del s2c s1c +permutation s1rc s2rc s1del s2ins s1c s2c + +# Test the same scenarios in SERIALIZABLE: +# We should report the FK violation: +permutation s1ser s2ser s2ins s1del s2c s1c +# We raise a concurrent update error +# which is good enough: +permutation s1ser s2ser s1del s2ins s1c s2c + +# Also check updating the valid time (without violating RI): + +# ...with READ COMMITED: +permutation s1rc s2rc s2ins s1upok s2c s1c +permutation s1rc s2rc s1upok s2ins s1c s2c +# ...with REPEATABLE READ: +permutation s1rr s2rr s2ins s1upok s2c s1c +permutation s1rr s2rr s1upok s2ins s1c s2c +# ...with SERIALIZABLE: +permutation s1ser s2ser s2ins s1upok s2c s1c +permutation s1ser s2ser s1upok s2ins s1c s2c + +# Also check updating the valid time (while violating RI): + +# ...with READ COMMITED: +permutation s1rc s2rc s2ins s1upbad s2c s1c +permutation s1rc s2rc s1upbad s2ins s1c s2c +# ...with REPEATABLE READ: +permutation s1rr s2rr s2ins s1upbad s2c s1c +permutation s1rr s2rr s1upbad s2ins s1c s2c +# ...with SERIALIZABLE: +permutation s1ser s2ser s2ins s1upbad s2c s1c +permutation s1ser s2ser s1upbad s2ins s1c s2c From 1d5800019f68d81139021b8bab159b8578fcaa2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 11 Sep 2025 19:49:57 +0200 Subject: [PATCH 40/96] Improve comment about snapshot macros The comment mistakenly had "the others" for "the other", but this commit also reorders the comment so it matches the macros below. Now we describe the levels in increasing strictness. In addition, it seems easier to follow if we introduce one level at a time, rather than describing two, followed by "the other" (and then jumping back to one of the first two). Finally, reword the sentence about the purpose of the macros, which was slightly off-point. Author: Paul Jungwirth Reviewed-by: Rustam ALLAKOV Reviewed-by: Dean Rasheed Reviewed-by: Robert Haas Discussion: https://postgr.es/m/CA+renyUp=xja80rBaB6NpY3RRdi750y046x28bo_xg29zKY72Q@mail.gmail.com --- src/include/access/xact.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/include/access/xact.h b/src/include/access/xact.h index b2bc10ee04196..4528e51829e61 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -43,10 +43,11 @@ extern PGDLLIMPORT int XactIsoLevel; /* * We implement three isolation levels internally. - * The two stronger ones use one snapshot per database transaction; - * the others use one snapshot per statement. - * Serializable uses predicate locks in addition to snapshots. - * These macros should be used to check which isolation level is selected. + * The weakest uses one snapshot per statement; + * the two stronger levels use one snapshot per database transaction. + * Serializable uses predicate locks in addition to the snapshot. + * These macros can be used to determine which implementation to use + * depending on the prevailing serialization level. */ #define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ) #define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE) From a0b99fc12203fa179d5b4218a21de30e0e91a7b8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 11 Sep 2025 17:11:54 -0400 Subject: [PATCH 41/96] Report the correct is_temporary flag for column defaults. pg_event_trigger_dropped_objects() would report a column default object with is_temporary = false, even if it belongs to a temporary table. This seems clearly wrong, so adjust it to report the table's temp-ness. While here, refactor EventTriggerSQLDropAddObject to make its handling of namespace objects less messy and avoid duplication of the schema-lookup code. And add some explicit test coverage of dropped-object reports for dependencies of temp tables. Back-patch to v15. The bug exists further back, but the GetAttrDefaultColumnAddress function this patch depends on does not, and it doesn't seem worth adjusting it to cope with the older code. Author: Antoine Violin Co-authored-by: Tom Lane Discussion: https://postgr.es/m/CAFjUV9x3-hv0gihf+CtUc-1it0hh7Skp9iYFhMS7FJjtAeAptA@mail.gmail.com Backpatch-through: 15 --- src/backend/commands/event_trigger.c | 111 ++++++++++++++------ src/test/regress/expected/event_trigger.out | 37 +++++++ src/test/regress/sql/event_trigger.sql | 20 ++++ 3 files changed, 136 insertions(+), 32 deletions(-) diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 631fb0525f1e7..fcdcba009d4e3 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -21,6 +21,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" +#include "catalog/pg_attrdef.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" #include "catalog/pg_database.h" @@ -109,6 +110,8 @@ static Oid insert_event_trigger_tuple(const char *trigname, const char *eventnam static void validate_ddl_tags(const char *filtervar, List *taglist); static void validate_table_rewrite_tags(const char *filtervar, List *taglist); static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata); +static bool obtain_object_name_namespace(const ObjectAddress *object, + SQLDropObject *obj); static const char *stringify_grant_objtype(ObjectType objtype); static const char *stringify_adefprivs_objtype(ObjectType objtype); static void SetDatabaseHasLoginEventTriggers(void); @@ -1280,12 +1283,6 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no Assert(EventTriggerSupportsObject(object)); - /* don't report temp schemas except my own */ - if (object->classId == NamespaceRelationId && - (isAnyTempNamespace(object->objectId) && - !isTempNamespace(object->objectId))) - return; - oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); obj = palloc0(sizeof(SQLDropObject)); @@ -1293,21 +1290,88 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no obj->original = original; obj->normal = normal; + if (object->classId == NamespaceRelationId) + { + /* Special handling is needed for temp namespaces */ + if (isTempNamespace(object->objectId)) + obj->istemp = true; + else if (isAnyTempNamespace(object->objectId)) + { + /* don't report temp schemas except my own */ + pfree(obj); + MemoryContextSwitchTo(oldcxt); + return; + } + } + else if (object->classId == AttrDefaultRelationId) + { + /* We treat a column default as temp if its table is temp */ + ObjectAddress colobject; + + colobject = GetAttrDefaultColumnAddress(object->objectId); + if (OidIsValid(colobject.objectId)) + { + colobject.objectSubId = 0; /* convert to table reference */ + if (!obtain_object_name_namespace(&colobject, obj)) + { + pfree(obj); + MemoryContextSwitchTo(oldcxt); + return; + } + } + } + else + { + /* Generic handling for all other object classes */ + if (!obtain_object_name_namespace(object, obj)) + { + /* don't report temp objects except my own */ + pfree(obj); + MemoryContextSwitchTo(oldcxt); + return; + } + } + + /* object identity, objname and objargs */ + obj->objidentity = + getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs, + false); + + /* object type */ + obj->objecttype = getObjectTypeDescription(&obj->address, false); + + slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Fill obj->objname, obj->schemaname, and obj->istemp based on object. + * + * Returns true if this object should be reported, false if it should + * be ignored because it is a temporary object of another session. + */ +static bool +obtain_object_name_namespace(const ObjectAddress *object, SQLDropObject *obj) +{ /* * Obtain schema names from the object's catalog tuple, if one exists; * this lets us skip objects in temp schemas. We trust that * ObjectProperty contains all object classes that can be * schema-qualified. + * + * Currently, this function does nothing for object classes that are not + * in ObjectProperty, but we might sometime add special cases for that. */ if (is_objectclass_supported(object->classId)) { Relation catalog; HeapTuple tuple; - catalog = table_open(obj->address.classId, AccessShareLock); + catalog = table_open(object->classId, AccessShareLock); tuple = get_catalog_object_by_oid(catalog, get_object_attnum_oid(object->classId), - obj->address.objectId); + object->objectId); if (tuple) { @@ -1315,7 +1379,7 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no Datum datum; bool isnull; - attnum = get_object_attnum_namespace(obj->address.classId); + attnum = get_object_attnum_namespace(object->classId); if (attnum != InvalidAttrNumber) { datum = heap_getattr(tuple, attnum, @@ -1333,10 +1397,9 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no } else if (isAnyTempNamespace(namespaceId)) { - pfree(obj); + /* no need to fill any fields of *obj */ table_close(catalog, AccessShareLock); - MemoryContextSwitchTo(oldcxt); - return; + return false; } else { @@ -1346,10 +1409,10 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no } } - if (get_object_namensp_unique(obj->address.classId) && - obj->address.objectSubId == 0) + if (get_object_namensp_unique(object->classId) && + object->objectSubId == 0) { - attnum = get_object_attnum_name(obj->address.classId); + attnum = get_object_attnum_name(object->classId); if (attnum != InvalidAttrNumber) { datum = heap_getattr(tuple, attnum, @@ -1362,24 +1425,8 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no table_close(catalog, AccessShareLock); } - else - { - if (object->classId == NamespaceRelationId && - isTempNamespace(object->objectId)) - obj->istemp = true; - } - /* object identity, objname and objargs */ - obj->objidentity = - getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs, - false); - - /* object type */ - obj->objecttype = getObjectTypeDescription(&obj->address, false); - - slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next); - - MemoryContextSwitchTo(oldcxt); + return true; } /* diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 7b2198eac6f20..0e090cbc37500 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -476,6 +476,43 @@ NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_15 NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_15_20 name={evttrig,part_15_20} args={} DROP TABLE a_temp_tbl; NOTICE: NORMAL: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={} +-- check unfiltered results, too +CREATE OR REPLACE FUNCTION event_trigger_report_dropped() + RETURNS event_trigger + LANGUAGE plpgsql +AS $$ +DECLARE r record; +BEGIN + FOR r IN SELECT * from pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% name=% args=%', + r.original, r.normal, r.is_temporary, r.object_type, + r.object_identity, r.address_names, r.address_args; + END LOOP; +END; $$; +NOTICE: END: command_tag=CREATE FUNCTION type=function identity=public.event_trigger_report_dropped() +CREATE TABLE evtrg_nontemp_table (f1 int primary key, f2 int default 42); +NOTICE: END: command_tag=CREATE TABLE type=table identity=public.evtrg_nontemp_table +NOTICE: END: command_tag=CREATE INDEX type=index identity=public.evtrg_nontemp_table_pkey +DROP TABLE evtrg_nontemp_table; +NOTICE: DROP: orig=t normal=f istemp=f type=table identity=public.evtrg_nontemp_table name={public,evtrg_nontemp_table} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table name={public.evtrg_nontemp_table} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table[] name={public.evtrg_nontemp_table[]} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=default value identity=for public.evtrg_nontemp_table.f2 name={public,evtrg_nontemp_table,f2} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_f1_not_null on public.evtrg_nontemp_table name={public,evtrg_nontemp_table,evtrg_nontemp_table_f1_not_null} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_pkey on public.evtrg_nontemp_table name={public,evtrg_nontemp_table,evtrg_nontemp_table_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=index identity=public.evtrg_nontemp_table_pkey name={public,evtrg_nontemp_table_pkey} args={} +CREATE TEMP TABLE a_temp_tbl (f1 int primary key, f2 int default 42); +NOTICE: END: command_tag=CREATE TABLE type=table identity=pg_temp.a_temp_tbl +NOTICE: END: command_tag=CREATE INDEX type=index identity=pg_temp.a_temp_tbl_pkey +DROP TABLE a_temp_tbl; +NOTICE: DROP: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl name={pg_temp.a_temp_tbl} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl[] name={pg_temp.a_temp_tbl[]} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=default value identity=for pg_temp.a_temp_tbl.f2 name={pg_temp,a_temp_tbl,f2} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_f1_not_null on pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl,a_temp_tbl_f1_not_null} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_pkey on pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl,a_temp_tbl_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=index identity=pg_temp.a_temp_tbl_pkey name={pg_temp,a_temp_tbl_pkey} args={} -- CREATE OPERATOR CLASS without FAMILY clause should report -- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int; diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index 013546b83057b..ef5978b9697aa 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -337,6 +337,26 @@ DROP INDEX evttrig.one_idx; DROP SCHEMA evttrig CASCADE; DROP TABLE a_temp_tbl; +-- check unfiltered results, too +CREATE OR REPLACE FUNCTION event_trigger_report_dropped() + RETURNS event_trigger + LANGUAGE plpgsql +AS $$ +DECLARE r record; +BEGIN + FOR r IN SELECT * from pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% name=% args=%', + r.original, r.normal, r.is_temporary, r.object_type, + r.object_identity, r.address_names, r.address_args; + END LOOP; +END; $$; + +CREATE TABLE evtrg_nontemp_table (f1 int primary key, f2 int default 42); +DROP TABLE evtrg_nontemp_table; +CREATE TEMP TABLE a_temp_tbl (f1 int primary key, f2 int default 42); +DROP TABLE a_temp_tbl; + -- CREATE OPERATOR CLASS without FAMILY clause should report -- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int; From ed1aad15e09d7d523f4ef413e3c4d410497c8065 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Thu, 11 Sep 2025 16:13:55 -0500 Subject: [PATCH 42/96] Move named LWLock tranche requests to shared memory. In EXEC_BACKEND builds, GetNamedLWLockTranche() can segfault when called outside of the postmaster process, as it might access NamedLWLockTrancheRequestArray, which won't be initialized. Given the lack of reports, this is apparently unusual, presumably because it is usually called from a shmem_startup_hook like this: mystruct = ShmemInitStruct(..., &found); if (!found) { mystruct->locks = GetNamedLWLockTranche(...); ... } This genre of shmem_startup_hook evades the aforementioned segfaults because the struct is initialized in the postmaster, so all other callers skip the !found path. We considered modifying the documentation or requiring GetNamedLWLockTranche() to be called from the postmaster, but ultimately we decided to simply move the request array to shared memory (and add it to the BackendParameters struct), thereby allowing calls outside postmaster on all platforms. Since the main shared memory segment is initialized after accepting LWLock tranche requests, postmaster builds the request array in local memory first and then copies it to shared memory later. Given the lack of reports, back-patching seems unnecessary. Reported-by: Sami Imseih Reviewed-by: Sami Imseih Discussion: https://postgr.es/m/CAA5RZ0v1_15QPg5Sqd2Qz5rh_qcsyCeHHmRDY89xVHcy2yt5BQ%40mail.gmail.com --- src/backend/postmaster/launch_backend.c | 3 +++ src/backend/storage/lmgr/lwlock.c | 31 +++++++++++++++++++++---- src/include/storage/lwlock.h | 4 ++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index a38979c50e4bb..c5ef14e1eaae8 100644 --- a/src/backend/postmaster/launch_backend.c +++ b/src/backend/postmaster/launch_backend.c @@ -101,6 +101,7 @@ typedef struct struct InjectionPointsCtl *ActiveInjectionPoints; #endif int NamedLWLockTrancheRequests; + NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray; char **LWLockTrancheNames; int *LWLockCounter; LWLockPadded *MainLWLockArray; @@ -761,6 +762,7 @@ save_backend_variables(BackendParameters *param, #endif param->NamedLWLockTrancheRequests = NamedLWLockTrancheRequests; + param->NamedLWLockTrancheRequestArray = NamedLWLockTrancheRequestArray; param->LWLockTrancheNames = LWLockTrancheNames; param->LWLockCounter = LWLockCounter; param->MainLWLockArray = MainLWLockArray; @@ -1022,6 +1024,7 @@ restore_backend_variables(BackendParameters *param) #endif NamedLWLockTrancheRequests = param->NamedLWLockTrancheRequests; + NamedLWLockTrancheRequestArray = param->NamedLWLockTrancheRequestArray; LWLockTrancheNames = param->LWLockTrancheNames; LWLockCounter = param->LWLockCounter; MainLWLockArray = param->MainLWLockArray; diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index fcbac5213a5c0..46c82c63ca537 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -184,14 +184,13 @@ typedef struct NamedLWLockTrancheRequest int num_lwlocks; } NamedLWLockTrancheRequest; -static NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray = NULL; - /* - * NamedLWLockTrancheRequests is the valid length of the request array. This - * variable is non-static so that postmaster.c can copy them to child processes - * in EXEC_BACKEND builds. + * NamedLWLockTrancheRequests is the valid length of the request array. These + * variables are non-static so that launch_backend.c can copy them to child + * processes in EXEC_BACKEND builds. */ int NamedLWLockTrancheRequests = 0; +NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray = NULL; /* shared memory counter of registered tranches */ int *LWLockCounter = NULL; @@ -407,6 +406,14 @@ LWLockShmemSize(void) size = add_size(size, mul_size(MAX_NAMED_TRANCHES, sizeof(char *))); size = add_size(size, mul_size(MAX_NAMED_TRANCHES, NAMEDATALEN)); + /* + * Make space for named tranche requests. This is done for the benefit of + * EXEC_BACKEND builds, which otherwise wouldn't be able to call + * GetNamedLWLockTranche() outside postmaster. + */ + size = add_size(size, mul_size(NamedLWLockTrancheRequests, + sizeof(NamedLWLockTrancheRequest))); + /* Space for the LWLock array, plus room for cache line alignment. */ size = add_size(size, LWLOCK_PADDED_SIZE); size = add_size(size, mul_size(numLocks, sizeof(LWLockPadded))); @@ -443,6 +450,20 @@ CreateLWLocks(void) ptr += NAMEDATALEN; } + /* + * Move named tranche requests to shared memory. This is done for the + * benefit of EXEC_BACKEND builds, which otherwise wouldn't be able to + * call GetNamedLWLockTranche() outside postmaster. + */ + if (NamedLWLockTrancheRequests > 0) + { + memcpy(ptr, NamedLWLockTrancheRequestArray, + NamedLWLockTrancheRequests * sizeof(NamedLWLockTrancheRequest)); + pfree(NamedLWLockTrancheRequestArray); + NamedLWLockTrancheRequestArray = (NamedLWLockTrancheRequest *) ptr; + ptr += NamedLWLockTrancheRequests * sizeof(NamedLWLockTrancheRequest); + } + /* Ensure desired alignment of LWLock array */ ptr += LWLOCK_PADDED_SIZE - ((uintptr_t) ptr) % LWLOCK_PADDED_SIZE; MainLWLockArray = (LWLockPadded *) ptr; diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index 0e9cf81a4c766..8e0d0d233b48f 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -73,8 +73,12 @@ typedef union LWLockPadded extern PGDLLIMPORT LWLockPadded *MainLWLockArray; +/* forward declaration of private type for use only by lwlock.c */ +typedef struct NamedLWLockTrancheRequest NamedLWLockTrancheRequest; + extern PGDLLIMPORT char **LWLockTrancheNames; extern PGDLLIMPORT int NamedLWLockTrancheRequests; +extern PGDLLIMPORT NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray; extern PGDLLIMPORT int *LWLockCounter; /* From 306dd13079ed616c414c9411c5deadffea273266 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 12 Sep 2025 09:55:16 +0900 Subject: [PATCH 43/96] Remove whitespace in comment of pg_stat_statements.c Introduced by 6b4d23feef6e, spotted while reading this area of the code. --- contrib/pg_stat_statements/pg_stat_statements.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 1cb368c8590ba..0bb0f9333998b 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -139,7 +139,6 @@ typedef enum pgssStoreKind * If you add a new key to this struct, make sure to teach pgss_store() to * zero the padding bytes. Otherwise, things will break, because pgss_hash is * created using HASH_BLOBS, and thus tag_hash is used to hash this. - */ typedef struct pgssHashKey { From 528dadf691df3023fbb0bd71da5c6087c2d49d6a Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 12 Sep 2025 10:29:02 +0900 Subject: [PATCH 44/96] Add more information for WAL records of hash index AMs hashdesc.c was missing a couple of fields in its record descriptions, as of: - is_prev_bucket_same_wrt for SQUEEZE_PAGE. - procid for INIT_META_PAGE. - old_bucket_flag and new_bucket_flag for SPLIT_ALLOCATE_PAGE. The author has noted the first hole, and I have spotted the others while double-checking this area of the code. Note that the only data missing now are the offsets stored in VACUUM_ONE_PAGE. We could perhaps add them, if somebody sees value in this data, even if it makes the output larger. These are discarded here. Author: Kirill Reshke Discussion: https://postgr.es/m/CALdSSPjc-OVwtZH0Xrkvg7n=2ZwdbMJzqrm_ed_CfjiAzuKVGg@mail.gmail.com --- src/backend/access/rmgrdesc/hashdesc.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/backend/access/rmgrdesc/hashdesc.c b/src/backend/access/rmgrdesc/hashdesc.c index 75f43a9152071..2ee5332452f39 100644 --- a/src/backend/access/rmgrdesc/hashdesc.c +++ b/src/backend/access/rmgrdesc/hashdesc.c @@ -28,8 +28,10 @@ hash_desc(StringInfo buf, XLogReaderState *record) { xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) rec; - appendStringInfo(buf, "num_tuples %g, fillfactor %d", - xlrec->num_tuples, xlrec->ffactor); + appendStringInfo(buf, "num_tuples %g, procid %u, fillfactor %d", + xlrec->num_tuples, + xlrec->procid, + xlrec->ffactor); break; } case XLOG_HASH_INIT_BITMAP_PAGE: @@ -58,8 +60,10 @@ hash_desc(StringInfo buf, XLogReaderState *record) { xl_hash_split_allocate_page *xlrec = (xl_hash_split_allocate_page *) rec; - appendStringInfo(buf, "new_bucket %u, meta_page_masks_updated %c, issplitpoint_changed %c", + appendStringInfo(buf, "new_bucket %u, old_bucket_flag %u, new_bucket_flag %u, meta_page_masks_updated %c, issplitpoint_changed %c", xlrec->new_bucket, + xlrec->old_bucket_flag, + xlrec->new_bucket_flag, (xlrec->flags & XLH_SPLIT_META_UPDATE_MASKS) ? 'T' : 'F', (xlrec->flags & XLH_SPLIT_META_UPDATE_SPLITPOINT) ? 'T' : 'F'); break; @@ -85,11 +89,12 @@ hash_desc(StringInfo buf, XLogReaderState *record) { xl_hash_squeeze_page *xlrec = (xl_hash_squeeze_page *) rec; - appendStringInfo(buf, "prevblkno %u, nextblkno %u, ntups %d, is_primary %c", + appendStringInfo(buf, "prevblkno %u, nextblkno %u, ntups %d, is_primary %c, is_previous %c", xlrec->prevblkno, xlrec->nextblkno, xlrec->ntups, - xlrec->is_prim_bucket_same_wrt ? 'T' : 'F'); + xlrec->is_prim_bucket_same_wrt ? 'T' : 'F', + xlrec->is_prev_bucket_same_wrt ? 'T' : 'F'); break; } case XLOG_HASH_DELETE: From 2d756ebbe857e3d395d18350bf232300ebd23981 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Fri, 12 Sep 2025 11:12:19 +0900 Subject: [PATCH 45/96] Fix misuse of Relids for storing attribute numbers The typedef Relids (Bitmapset *) is intended to represent set of relation identifiers, but was incorrectly used in several places to store sets of attribute numbers. This is my oversight in e2debb643. Fix that by replacing such usages with Bitmapset * to reflect the correct semantics. Author: Junwang Zhao Reviewed-by: Tender Wang Reviewed-by: Richard Guo Discussion: https://postgr.es/m/CAEG8a3LJhp_xriXf39iCz0TsK+M-2biuhDhpLC6Baxw8+ZYT3A@mail.gmail.com --- src/backend/optimizer/util/clauses.c | 2 +- src/backend/optimizer/util/plancat.c | 6 +++--- src/include/optimizer/plancat.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 6f0b338d2cdf1..ae0bd073ca917 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4203,7 +4203,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info) { - Relids notnullattnums = NULL; + Bitmapset *notnullattnums = NULL; Assert(IsA(var, Var)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 4536bdd6cb4d7..572d626b2c4d2 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -62,7 +62,7 @@ get_relation_info_hook_type get_relation_info_hook = NULL; typedef struct NotnullHashEntry { Oid relid; /* OID of the relation */ - Relids notnullattnums; /* attnums of NOT NULL columns */ + Bitmapset *notnullattnums; /* attnums of NOT NULL columns */ } NotnullHashEntry; @@ -683,7 +683,7 @@ get_relation_notnullatts(PlannerInfo *root, Relation relation) Oid relid = RelationGetRelid(relation); NotnullHashEntry *hentry; bool found; - Relids notnullattnums = NULL; + Bitmapset *notnullattnums = NULL; /* bail out if the relation has no not-null constraints */ if (relation->rd_att->constr == NULL || @@ -750,7 +750,7 @@ get_relation_notnullatts(PlannerInfo *root, Relation relation) * Searches the hash table and returns the column not-null constraint * information for a given relation. */ -Relids +Bitmapset * find_relation_notnullatts(PlannerInfo *root, Oid relid) { NotnullHashEntry *hentry; diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index dd8f2cd157f6f..9610707683235 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -30,7 +30,7 @@ extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, extern void get_relation_notnullatts(PlannerInfo *root, Relation relation); -extern Relids find_relation_notnullatts(PlannerInfo *root, Oid relid); +extern Bitmapset *find_relation_notnullatts(PlannerInfo *root, Oid relid); extern List *infer_arbiter_indexes(PlannerInfo *root); From e92677e86333562b8dd4972083c8a1abf985d90d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 12 Sep 2025 07:27:48 +0200 Subject: [PATCH 46/96] Silence compiler warnings on clang 21 Clang 21 shows some new compiler warnings, for example: warning: variable 'dstsize' is uninitialized when passed as a const pointer argument here [-Wuninitialized-const-pointer] The fix is to initialize the variables when they are defined. This is similar to, for example, the existing situation in gistKeyIsEQ(). Discussion: https://www.postgresql.org/message-id/flat/6604ad6e-5934-43ac-8590-15113d6ae4b1%40eisentraut.org --- src/backend/access/common/toast_internals.c | 2 +- src/backend/access/gist/gistutil.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index a1d0eed8953ba..75e908c2e80a7 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -135,7 +135,7 @@ toast_save_datum(Relation rel, Datum value, char data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ]; /* ensure union is aligned well enough: */ int32 align_it; - } chunk_data; + } chunk_data = {0}; /* silence compiler warning */ int32 chunk_size; int32 chunk_seq = 0; char *data_p; diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index c0aa7d0222f39..cdc4ab3151be1 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -157,7 +157,7 @@ gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, { int i; GistEntryVector *evec; - int attrsize; + int attrsize = 0; /* silence compiler warning */ evec = (GistEntryVector *) palloc((len + 2) * sizeof(GISTENTRY) + GEVHDRSZ); @@ -242,7 +242,7 @@ gistMakeUnionKey(GISTSTATE *giststate, int attno, char padding[2 * sizeof(GISTENTRY) + GEVHDRSZ]; } storage; GistEntryVector *evec = &storage.gev; - int dstsize; + int dstsize = 0; /* silence compiler warning */ evec->n = 2; From 25f36066dd2abde74faa12f08e5e498a95128cd0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 11 Sep 2025 11:55:29 +0200 Subject: [PATCH 47/96] Remove traces of support for Sun Studio compiler Per discussion, this compiler suite is no longer maintained, and it has not been able to compile PostgreSQL since at least PostgreSQL 17. This removes all the remaining support code for this compiler. Note that the Solaris operating system continues to be supported, but using GCC as the compiler. Reviewed-by: Andres Freund Reviewed-by: Tom Lane Discussion: https://www.postgresql.org/message-id/flat/a0f817ee-fb86-483a-8a14-b6f7f5991b6e%40eisentraut.org --- config/c-compiler.m4 | 2 +- configure | 53 +--------- configure.ac | 31 +----- doc/src/sgml/dfunc.sgml | 9 +- doc/src/sgml/installation.sgml | 60 +----------- meson.build | 2 +- src/Makefile.global.in | 4 - src/backend/port/Makefile | 12 --- src/backend/port/tas/sunstudio_sparc.s | 53 ---------- src/backend/port/tas/sunstudio_x86.s | 43 -------- src/backend/storage/lmgr/Makefile | 6 +- src/include/c.h | 15 ++- src/include/port/atomics.h | 2 - src/include/port/atomics/arch-x86.h | 2 +- src/include/port/atomics/generic-sunpro.h | 113 ---------------------- src/include/port/solaris.h | 21 ---- src/include/storage/s_lock.h | 24 +---- src/makefiles/meson.build | 4 +- src/template/linux | 23 ----- src/template/solaris | 29 +----- src/tools/pginclude/headerscheck | 1 - 21 files changed, 24 insertions(+), 485 deletions(-) delete mode 100644 src/backend/port/tas/sunstudio_sparc.s delete mode 100644 src/backend/port/tas/sunstudio_x86.s delete mode 100644 src/include/port/atomics/generic-sunpro.h diff --git a/config/c-compiler.m4 b/config/c-compiler.m4 index da40bd6a64755..236a59e8536c2 100644 --- a/config/c-compiler.m4 +++ b/config/c-compiler.m4 @@ -83,7 +83,7 @@ if test x"$pgac_cv__128bit_int" = xyes ; then AC_CACHE_CHECK([for __int128 alignment bug], [pgac_cv__128bit_int_bug], [AC_RUN_IFELSE([AC_LANG_PROGRAM([ /* This must match the corresponding code in c.h: */ -#if defined(__GNUC__) || defined(__SUNPRO_C) +#if defined(__GNUC__) #define pg_attribute_aligned(a) __attribute__((aligned(a))) #elif defined(_MSC_VER) #define pg_attribute_aligned(a) __declspec(align(a)) diff --git a/configure b/configure index 39c68161ceced..22cd866147b96 100755 --- a/configure +++ b/configure @@ -739,7 +739,6 @@ PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG DLSUFFIX -TAS GCC CPP CFLAGS_SL @@ -760,7 +759,6 @@ CLANG LLVM_CONFIG AWK with_llvm -SUN_STUDIO_CC ac_ct_CXX CXXFLAGS CXX @@ -3059,12 +3057,6 @@ $as_echo "$template" >&6; } PORTNAME=$template -# Initialize default assumption that we do not need separate assembly code -# for TAS (test-and-set). This can be overridden by the template file -# when it's executed. -need_tas=no -tas_file=dummy.s - # Default, works for most platforms, override in template file if needed DLSUFFIX=".so" @@ -4799,30 +4791,6 @@ else fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -# Check if it's Sun Studio compiler. We assume that -# __SUNPRO_C will be defined for Sun Studio compilers -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ -#ifndef __SUNPRO_C -choke me -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - SUN_STUDIO_CC=yes -else - SUN_STUDIO_CC=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - - - # # LLVM @@ -6748,7 +6716,7 @@ fi # __attribute__((visibility("hidden"))) is supported, if we encounter a # compiler that supports one of the supported variants of -fvisibility=hidden # but uses a different syntax to mark a symbol as exported. -if test "$GCC" = yes -o "$SUN_STUDIO_CC" = yes ; then +if test "$GCC" = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -fvisibility=hidden, for CFLAGS_SL_MODULE" >&5 $as_echo_n "checking whether ${CC} supports -fvisibility=hidden, for CFLAGS_SL_MODULE... " >&6; } if ${pgac_cv_prog_CC_cflags__fvisibility_hidden+:} false; then : @@ -7731,20 +7699,6 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -# -# Set up TAS assembly code if needed; the template file has now had its -# chance to request this. -# -ac_config_links="$ac_config_links src/backend/port/tas.s:src/backend/port/tas/${tas_file}" - - -if test "$need_tas" = yes ; then - TAS=tas.o -else - TAS="" -fi - - cat >>confdefs.h <<_ACEOF #define DLSUFFIX "$DLSUFFIX" @@ -17141,7 +17095,7 @@ else /* end confdefs.h. */ /* This must match the corresponding code in c.h: */ -#if defined(__GNUC__) || defined(__SUNPRO_C) +#if defined(__GNUC__) #define pg_attribute_aligned(a) __attribute__((aligned(a))) #elif defined(_MSC_VER) #define pg_attribute_aligned(a) __declspec(align(a)) @@ -19344,8 +19298,6 @@ fi if test x"$GCC" = x"yes" ; then cc_string=`${CC} --version | sed q` case $cc_string in [A-Za-z]*) ;; *) cc_string="GCC $cc_string";; esac -elif test x"$SUN_STUDIO_CC" = x"yes" ; then - cc_string=`${CC} -V 2>&1 | sed q` else cc_string=$CC fi @@ -20142,7 +20094,6 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 for ac_config_target in $ac_config_targets do case $ac_config_target in - "src/backend/port/tas.s") CONFIG_LINKS="$CONFIG_LINKS src/backend/port/tas.s:src/backend/port/tas/${tas_file}" ;; "GNUmakefile") CONFIG_FILES="$CONFIG_FILES GNUmakefile" ;; "src/Makefile.global") CONFIG_FILES="$CONFIG_FILES src/Makefile.global" ;; "src/backend/port/pg_sema.c") CONFIG_LINKS="$CONFIG_LINKS src/backend/port/pg_sema.c:${SEMA_IMPLEMENTATION}" ;; diff --git a/configure.ac b/configure.ac index 066e3976c0aac..e44943aa6fe35 100644 --- a/configure.ac +++ b/configure.ac @@ -95,12 +95,6 @@ AC_MSG_RESULT([$template]) PORTNAME=$template AC_SUBST(PORTNAME) -# Initialize default assumption that we do not need separate assembly code -# for TAS (test-and-set). This can be overridden by the template file -# when it's executed. -need_tas=no -tas_file=dummy.s - # Default, works for most platforms, override in template file if needed DLSUFFIX=".so" @@ -400,14 +394,6 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [@%:@ifndef __INTEL_COMPILER choke me @%:@endif])], [ICC=yes], [ICC=no]) -# Check if it's Sun Studio compiler. We assume that -# __SUNPRO_C will be defined for Sun Studio compilers -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [@%:@ifndef __SUNPRO_C -choke me -@%:@endif])], [SUN_STUDIO_CC=yes], [SUN_STUDIO_CC=no]) - -AC_SUBST(SUN_STUDIO_CC) - # # LLVM @@ -618,7 +604,7 @@ fi # __attribute__((visibility("hidden"))) is supported, if we encounter a # compiler that supports one of the supported variants of -fvisibility=hidden # but uses a different syntax to mark a symbol as exported. -if test "$GCC" = yes -o "$SUN_STUDIO_CC" = yes ; then +if test "$GCC" = yes; then PGAC_PROG_CC_VAR_OPT(CFLAGS_SL_MODULE, [-fvisibility=hidden]) # For C++ we additionally want -fvisibility-inlines-hidden PGAC_PROG_VARCXX_VARFLAGS_OPT(CXX, CXXFLAGS_SL_MODULE, [-fvisibility=hidden]) @@ -774,19 +760,6 @@ AC_PROG_CPP AC_SUBST(GCC) -# -# Set up TAS assembly code if needed; the template file has now had its -# chance to request this. -# -AC_CONFIG_LINKS([src/backend/port/tas.s:src/backend/port/tas/${tas_file}]) - -if test "$need_tas" = yes ; then - TAS=tas.o -else - TAS="" -fi -AC_SUBST(TAS) - AC_SUBST(DLSUFFIX)dnl AC_DEFINE_UNQUOTED([DLSUFFIX], ["$DLSUFFIX"], [Define to the file name extension of dynamically-loadable modules.]) @@ -2478,8 +2451,6 @@ AC_SUBST(LDFLAGS_EX_BE) if test x"$GCC" = x"yes" ; then cc_string=`${CC} --version | sed q` case $cc_string in [[A-Za-z]]*) ;; *) cc_string="GCC $cc_string";; esac -elif test x"$SUN_STUDIO_CC" = x"yes" ; then - cc_string=`${CC} -V 2>&1 | sed q` else cc_string=$CC fi diff --git a/doc/src/sgml/dfunc.sgml b/doc/src/sgml/dfunc.sgml index b94aefcd0ca6c..3778efc83ebfa 100644 --- a/doc/src/sgml/dfunc.sgml +++ b/doc/src/sgml/dfunc.sgml @@ -157,19 +157,12 @@ ld -Bshareable -o foo.so foo.o The compiler flag to create PIC is - with the Sun compiler and with GCC. To link shared libraries, the compiler option is - with either compiler or alternatively with GCC. -cc -KPIC -c foo.c -cc -G -o foo.so foo.o - - or - gcc -fPIC -c foo.c -gcc -G -o foo.so foo.o +gcc -shared -o foo.so foo.o diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index a4ad80a678211..593202f4fb259 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -1676,10 +1676,6 @@ build-postgresql: using the GCC compiler: ./configure CC='gcc -m64' --enable-dtrace DTRACEFLAGS='-64' ... - - Using Sun's compiler: - -./configure CC='/opt/SUNWspro/bin/cc -xtarget=native64' --enable-dtrace DTRACEFLAGS='-64' ... @@ -3713,24 +3709,13 @@ xcrun --show-sdk-path Required Tools - You can build with either GCC or Sun's compiler suite. For - better code optimization, Sun's compiler is strongly recommended - on the SPARC architecture. If - you are using Sun's compiler, be careful not to select - /usr/ucb/cc; - use /opt/SUNWspro/bin/cc. + Only GCC is supported as the compiler. Sun's compiler suite is no longer + supported. - You can download Sun Studio - from . - Many GNU tools are integrated into Solaris 10, or they are - present on the Solaris companion CD. If you need packages for - older versions of Solaris, you can find these tools - at . - If you prefer - sources, look - at . + Many additional dependencies can be installed via the package management + system. @@ -3753,27 +3738,6 @@ configure ... LDFLAGS="-R /usr/sfw/lib:/opt/sfw/lib:/usr/local/lib" - - Compiling for Optimal Performance - - - On the SPARC architecture, Sun Studio is strongly recommended for - compilation. Try using the optimization - flag to generate significantly faster binaries. Do not use any - flags that modify behavior of floating-point operations - and errno processing (e.g., - ). - - - - If you do not have a reason to use 64-bit binaries on SPARC, - prefer the 32-bit version. The 64-bit operations are slower and - 64-bit binaries are slower than the 32-bit variants. On the - other hand, 32-bit code on the AMD64 CPU family is not native, - so 32-bit code is significantly slower on that CPU family. - - - Using DTrace for Tracing PostgreSQL @@ -3781,22 +3745,6 @@ configure ... LDFLAGS="-R /usr/sfw/lib:/opt/sfw/lib:/usr/local/lib" Yes, using DTrace is possible. See for further information. - - - If you see the linking of the postgres executable abort with an - error message like: - -Undefined first referenced - symbol in file -AbortTransaction utils/probes.o -CommitTransaction utils/probes.o -ld: fatal: Symbol referencing errors. No output written to postgres -collect2: ld returned 1 exit status -make: *** [postgres] Error 1 - - your DTrace installation is too old to handle probes in static - functions. You need Solaris 10u4 or newer to use DTrace. - diff --git a/meson.build b/meson.build index ab8101d67b26d..d71c7c8267e79 100644 --- a/meson.build +++ b/meson.build @@ -1809,7 +1809,7 @@ if cc.links(''' if not meson.is_cross_build() r = cc.run(''' /* This must match the corresponding code in c.h: */ - #if defined(__GNUC__) || defined(__SUNPRO_C) + #if defined(__GNUC__) #define pg_attribute_aligned(a) __attribute__((aligned(a))) #elif defined(_MSC_VER) #define pg_attribute_aligned(a) __declspec(align(a)) diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 8b1b357beaa04..0aa389bc71012 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -267,7 +267,6 @@ endif # not PGXS CC = @CC@ GCC = @GCC@ -SUN_STUDIO_CC = @SUN_STUDIO_CC@ CXX = @CXX@ CFLAGS = @CFLAGS@ CFLAGS_SL = @CFLAGS_SL@ @@ -796,9 +795,6 @@ ifeq ($(PORTNAME),win32) LIBS += -lws2_32 endif -# Not really standard libc functions, used by the backend. -TAS = @TAS@ - ########################################################################## # diff --git a/src/backend/port/Makefile b/src/backend/port/Makefile index 47338d9922957..8613ac01aff6d 100644 --- a/src/backend/port/Makefile +++ b/src/backend/port/Makefile @@ -22,7 +22,6 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = \ - $(TAS) \ atomics.o \ pg_sema.o \ pg_shmem.o @@ -33,16 +32,5 @@ endif include $(top_srcdir)/src/backend/common.mk -tas.o: tas.s -ifeq ($(SUN_STUDIO_CC), yes) -# preprocess assembler file with cpp - $(CC) $(CFLAGS) -c -P $< - mv $*.i $*_cpp.s - $(CC) $(CFLAGS) -c $*_cpp.s -o $@ -else - $(CC) $(CFLAGS) -c $< -endif - clean: - rm -f tas_cpp.s $(MAKE) -C win32 clean diff --git a/src/backend/port/tas/sunstudio_sparc.s b/src/backend/port/tas/sunstudio_sparc.s deleted file mode 100644 index 8e0a0965b64ea..0000000000000 --- a/src/backend/port/tas/sunstudio_sparc.s +++ /dev/null @@ -1,53 +0,0 @@ -!------------------------------------------------------------------------- -! -! sunstudio_sparc.s -! compare and swap for Sun Studio on Sparc -! -! Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group -! Portions Copyright (c) 1994, Regents of the University of California -! -! IDENTIFICATION -! src/backend/port/tas/sunstudio_sparc.s -! -!------------------------------------------------------------------------- - -! Fortunately the Sun compiler can process cpp conditionals with -P - -! '/' is the comment for x86, while '!' is the comment for Sparc - -#if defined(__sparcv9) || defined(__sparc) - - .section ".text" - .align 8 - .skip 24 - .align 4 - - .global pg_atomic_cas -pg_atomic_cas: - - ! "cas" only works on sparcv9 and sparcv8plus chips, and - ! requires a compiler targeting these CPUs. It will fail - ! on a compiler targeting sparcv8, and of course will not - ! be understood by a sparcv8 CPU. gcc continues to use - ! "ldstub" because it targets sparcv7. - ! - ! There is actually a trick for embedding "cas" in a - ! sparcv8-targeted compiler, but it can only be run - ! on a sparcv8plus/v9 cpus: - ! - ! http://cvs.opensolaris.org/source/xref/on/usr/src/lib/libc/sparc/threads/sparc.il - ! - ! NB: We're assuming we're running on a TSO system here - solaris - ! userland luckily always has done so. - -#if defined(__sparcv9) || defined(__sparcv8plus) - cas [%o0],%o2,%o1 -#else - ldstub [%o0],%o1 -#endif - mov %o1,%o0 - retl - nop - .type pg_atomic_cas,2 - .size pg_atomic_cas,(.-pg_atomic_cas) -#endif diff --git a/src/backend/port/tas/sunstudio_x86.s b/src/backend/port/tas/sunstudio_x86.s deleted file mode 100644 index 0111ffde45c29..0000000000000 --- a/src/backend/port/tas/sunstudio_x86.s +++ /dev/null @@ -1,43 +0,0 @@ -/------------------------------------------------------------------------- -/ -/ sunstudio_x86.s -/ compare and swap for Sun Studio on x86 -/ -/ Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group -/ Portions Copyright (c) 1994, Regents of the University of California -/ -/ IDENTIFICATION -/ src/backend/port/tas/sunstudio_x86.s -/ -/------------------------------------------------------------------------- - -/ Fortunately the Sun compiler can process cpp conditionals with -P - -/ '/' is the comment for x86, while '!' is the comment for Sparc - - .file "tas.s" - -#if defined(__amd64) - .code64 -#endif - - .globl pg_atomic_cas - .type pg_atomic_cas, @function - - .section .text, "ax" - .align 16 - -pg_atomic_cas: -#if defined(__amd64) - movl %edx,%eax - lock - cmpxchgl %esi,(%rdi) -#else - movl 4(%esp), %edx - movl 8(%esp), %ecx - movl 12(%esp), %eax - lock - cmpxchgl %ecx, (%edx) -#endif - ret - .size pg_atomic_cas, . - pg_atomic_cas diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile index 6cbaf23b855f6..a5fbc24ddad6e 100644 --- a/src/backend/storage/lmgr/Makefile +++ b/src/backend/storage/lmgr/Makefile @@ -24,13 +24,9 @@ OBJS = \ include $(top_srcdir)/src/backend/common.mk -ifdef TAS -TASPATH = $(top_builddir)/src/backend/port/tas.o -endif - s_lock_test: s_lock.c $(top_builddir)/src/common/libpgcommon.a $(top_builddir)/src/port/libpgport.a $(CC) $(CPPFLAGS) $(CFLAGS) -DS_LOCK_TEST=1 $(srcdir)/s_lock.c \ - $(TASPATH) -L $(top_builddir)/src/common -lpgcommon \ + -L $(top_builddir)/src/common -lpgcommon \ -L $(top_builddir)/src/port -lpgport -lm -o s_lock_test lwlocknames.h: ../../../include/storage/lwlocklist.h ../../utils/activity/wait_event_names.txt generate-lwlocknames.pl diff --git a/src/include/c.h b/src/include/c.h index b580cfa7d3178..f303ba0605a40 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -114,7 +114,6 @@ * GCC: https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html * GCC: https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html * Clang: https://clang.llvm.org/docs/AttributeReference.html - * Sunpro: https://docs.oracle.com/cd/E18659_01/html/821-1384/gjzke.html */ /* @@ -157,7 +156,7 @@ */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define pg_noreturn _Noreturn -#elif defined(__GNUC__) || defined(__SUNPRO_C) +#elif defined(__GNUC__) #define pg_noreturn __attribute__((noreturn)) #elif defined(_MSC_VER) #define pg_noreturn __declspec(noreturn) @@ -233,8 +232,8 @@ #define pg_attribute_printf(f,a) #endif -/* GCC and Sunpro support aligned and packed */ -#if defined(__GNUC__) || defined(__SUNPRO_C) +/* GCC supports aligned and packed */ +#if defined(__GNUC__) #define pg_attribute_aligned(a) __attribute__((aligned(a))) #define pg_attribute_packed() __attribute__((packed)) #elif defined(_MSC_VER) @@ -259,8 +258,8 @@ * choose not to. But, if possible, don't force inlining in unoptimized * debug builds. */ -#if (defined(__GNUC__) && defined(__OPTIMIZE__)) || defined(__SUNPRO_C) -/* GCC and Sunpro support always_inline via __attribute__ */ +#if defined(__GNUC__) && defined(__OPTIMIZE__) +/* GCC supports always_inline via __attribute__ */ #define pg_attribute_always_inline __attribute__((always_inline)) inline #elif defined(_MSC_VER) /* MSVC has a special keyword for this */ @@ -276,8 +275,8 @@ * for proper cost attribution. Note that unlike the pg_attribute_XXX macros * above, this should be placed before the function's return type and name. */ -/* GCC and Sunpro support noinline via __attribute__ */ -#if defined(__GNUC__) || defined(__SUNPRO_C) +/* GCC supports noinline via __attribute__ */ +#if defined(__GNUC__) #define pg_noinline __attribute__((noinline)) /* msvc via declspec */ #elif defined(_MSC_VER) diff --git a/src/include/port/atomics.h b/src/include/port/atomics.h index 074136fe4c4a8..96f1858da9722 100644 --- a/src/include/port/atomics.h +++ b/src/include/port/atomics.h @@ -88,8 +88,6 @@ #include "port/atomics/generic-gcc.h" #elif defined(_MSC_VER) #include "port/atomics/generic-msvc.h" -#elif defined(__SUNPRO_C) && !defined(__GNUC__) -#include "port/atomics/generic-sunpro.h" #else /* Unknown compiler. */ #endif diff --git a/src/include/port/atomics/arch-x86.h b/src/include/port/atomics/arch-x86.h index 8983dd89d53d7..4ba2ccc05913d 100644 --- a/src/include/port/atomics/arch-x86.h +++ b/src/include/port/atomics/arch-x86.h @@ -241,6 +241,6 @@ pg_atomic_fetch_add_u64_impl(volatile pg_atomic_uint64 *ptr, int64 add_) */ #if defined(__i568__) || defined(__i668__) || /* gcc i586+ */ \ (defined(_M_IX86) && _M_IX86 >= 500) || /* msvc i586+ */ \ - defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) /* gcc, sunpro, msvc */ + defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) /* gcc, msvc */ #define PG_HAVE_8BYTE_SINGLE_COPY_ATOMICITY #endif /* 8 byte single-copy atomicity */ diff --git a/src/include/port/atomics/generic-sunpro.h b/src/include/port/atomics/generic-sunpro.h deleted file mode 100644 index 09bba0be2037d..0000000000000 --- a/src/include/port/atomics/generic-sunpro.h +++ /dev/null @@ -1,113 +0,0 @@ -/*------------------------------------------------------------------------- - * - * generic-sunpro.h - * Atomic operations for solaris' CC - * - * Portions Copyright (c) 2013-2025, PostgreSQL Global Development Group - * - * NOTES: - * - * Documentation: - * * manpage for atomic_cas(3C) - * http://www.unix.com/man-page/opensolaris/3c/atomic_cas/ - * http://docs.oracle.com/cd/E23824_01/html/821-1465/atomic-cas-3c.html - * - * src/include/port/atomics/generic-sunpro.h - * - * ------------------------------------------------------------------------- - */ - -#ifdef HAVE_MBARRIER_H -#include - -#define pg_compiler_barrier_impl() __compiler_barrier() - -#ifndef pg_memory_barrier_impl -/* - * Despite the name this is actually a full barrier. Expanding to mfence/ - * membar #StoreStore | #LoadStore | #StoreLoad | #LoadLoad on x86/sparc - * respectively. - */ -# define pg_memory_barrier_impl() __machine_rw_barrier() -#endif -#ifndef pg_read_barrier_impl -# define pg_read_barrier_impl() __machine_r_barrier() -#endif -#ifndef pg_write_barrier_impl -# define pg_write_barrier_impl() __machine_w_barrier() -#endif - -#endif /* HAVE_MBARRIER_H */ - -/* Older versions of the compiler don't have atomic.h... */ -#ifdef HAVE_ATOMIC_H - -#include - -#define PG_HAVE_ATOMIC_U32_SUPPORT -typedef struct pg_atomic_uint32 -{ - volatile uint32 value; -} pg_atomic_uint32; - -#define PG_HAVE_ATOMIC_U64_SUPPORT -typedef struct pg_atomic_uint64 -{ - /* - * Syntax to enforce variable alignment should be supported by versions - * supporting atomic.h, but it's hard to find accurate documentation. If - * it proves to be a problem, we'll have to add more version checks for 64 - * bit support. - */ - volatile uint64 value pg_attribute_aligned(8); -} pg_atomic_uint64; - -#endif /* HAVE_ATOMIC_H */ - - -#ifdef HAVE_ATOMIC_H - -#define PG_HAVE_ATOMIC_COMPARE_EXCHANGE_U32 -static inline bool -pg_atomic_compare_exchange_u32_impl(volatile pg_atomic_uint32 *ptr, - uint32 *expected, uint32 newval) -{ - bool ret; - uint32 current; - - current = atomic_cas_32(&ptr->value, *expected, newval); - ret = current == *expected; - *expected = current; - return ret; -} - -#define PG_HAVE_ATOMIC_EXCHANGE_U32 -static inline uint32 -pg_atomic_exchange_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 newval) -{ - return atomic_swap_32(&ptr->value, newval); -} - -#define PG_HAVE_ATOMIC_COMPARE_EXCHANGE_U64 -static inline bool -pg_atomic_compare_exchange_u64_impl(volatile pg_atomic_uint64 *ptr, - uint64 *expected, uint64 newval) -{ - bool ret; - uint64 current; - - AssertPointerAlignment(expected, 8); - current = atomic_cas_64(&ptr->value, *expected, newval); - ret = current == *expected; - *expected = current; - return ret; -} - -#define PG_HAVE_ATOMIC_EXCHANGE_U64 -static inline uint64 -pg_atomic_exchange_u64_impl(volatile pg_atomic_uint64 *ptr, uint64 newval) -{ - return atomic_swap_64(&ptr->value, newval); -} - -#endif /* HAVE_ATOMIC_H */ diff --git a/src/include/port/solaris.h b/src/include/port/solaris.h index 8ff40007c7f6a..c352361c81d83 100644 --- a/src/include/port/solaris.h +++ b/src/include/port/solaris.h @@ -1,26 +1,5 @@ /* src/include/port/solaris.h */ -/* - * Sort this out for all operating systems some time. The __xxx - * symbols are defined on both GCC and Solaris CC, although GCC - * doesn't document them. The __xxx__ symbols are only on GCC. - */ -#if defined(__i386) && !defined(__i386__) -#define __i386__ -#endif - -#if defined(__amd64) && !defined(__amd64__) -#define __amd64__ -#endif - -#if defined(__x86_64) && !defined(__x86_64__) -#define __x86_64__ -#endif - -#if defined(__sparc) && !defined(__sparc__) -#define __sparc__ -#endif - #if defined(__i386__) #include #endif diff --git a/src/include/storage/s_lock.h b/src/include/storage/s_lock.h index 2f73f9fcf57a2..7f8f566bd407f 100644 --- a/src/include/storage/s_lock.h +++ b/src/include/storage/s_lock.h @@ -333,9 +333,9 @@ tas(volatile slock_t *lock) slock_t _res; /* - * See comment in src/backend/port/tas/sunstudio_sparc.s for why this - * uses "ldstub", and that file uses "cas". gcc currently generates - * sparcv7-targeted binaries, so "cas" use isn't possible. + * "cas" would be better than "ldstub", but it is only present on + * sparcv8plus and later, while some platforms still support sparcv7 or + * sparcv8. Also, "cas" requires that the system be running in TSO mode. */ __asm__ __volatile__( " ldstub [%2], %0 \n" @@ -594,24 +594,6 @@ tas(volatile slock_t *lock) #if !defined(HAS_TEST_AND_SET) /* We didn't trigger above, let's try here */ -/* These are in sunstudio_(sparc|x86).s */ - -#if defined(__SUNPRO_C) && (defined(__i386) || defined(__x86_64__) || defined(__sparc__) || defined(__sparc)) -#define HAS_TEST_AND_SET - -#if defined(__i386) || defined(__x86_64__) || defined(__sparcv9) || defined(__sparcv8plus) -typedef unsigned int slock_t; -#else -typedef unsigned char slock_t; -#endif - -extern slock_t pg_atomic_cas(volatile slock_t *lock, slock_t with, - slock_t cmp); - -#define TAS(a) (pg_atomic_cas((a), 1, 0) != 0) -#endif - - #ifdef _MSC_VER typedef LONG slock_t; diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build index 54dbc059adac7..0def244c9011d 100644 --- a/src/makefiles/meson.build +++ b/src/makefiles/meson.build @@ -63,8 +63,6 @@ pgxs_kv = { 'DLSUFFIX': dlsuffix, 'EXEEXT': exesuffix, - 'SUN_STUDIO_CC': 'no', # not supported so far - # want the chosen option, rather than the library 'with_ssl' : ssl_library, 'with_uuid': uuidopt, @@ -179,7 +177,7 @@ pgxs_empty = [ 'WANTED_LANGUAGES', # Not needed because we don't build the server / PLs with the generated makefile - 'LIBOBJS', 'PG_CRC32C_OBJS', 'TAS', + 'LIBOBJS', 'PG_CRC32C_OBJS', 'PG_TEST_EXTRA', 'DTRACEFLAGS', # only server has dtrace probes diff --git a/src/template/linux b/src/template/linux index ec3302c4a223f..faefe64254a90 100644 --- a/src/template/linux +++ b/src/template/linux @@ -14,26 +14,3 @@ CFLAGS_SL="-fPIC" # If --enable-profiling is specified, we need -DLINUX_PROFILE PLATFORM_PROFILE_FLAGS="-DLINUX_PROFILE" - -if test "$SUN_STUDIO_CC" = "yes" ; then - CC="$CC -Xa" # relaxed ISO C mode - CFLAGS="-v" # -v is like gcc -Wall - if test "$enable_debug" != yes; then - CFLAGS="$CFLAGS -O" # any optimization breaks debug - fi - - # Pick the right test-and-set (TAS) code for the Sun compiler. - # We would like to use in-line assembler, but the compiler - # requires *.il files to be on every compile line, making - # the build system too fragile. - case $host_cpu in - sparc) - need_tas=yes - tas_file=sunstudio_sparc.s - ;; - i?86|x86_64) - need_tas=yes - tas_file=sunstudio_x86.s - ;; - esac -fi diff --git a/src/template/solaris b/src/template/solaris index f88b1cdad37f8..a4d8d38a8f852 100644 --- a/src/template/solaris +++ b/src/template/solaris @@ -1,31 +1,4 @@ # src/template/solaris # Extra CFLAGS for code that will go into a shared library -if test "$GCC" = yes ; then - CFLAGS_SL="-fPIC" -else - CFLAGS_SL="-KPIC" -fi - -if test "$SUN_STUDIO_CC" = yes ; then - CC="$CC -Xa" # relaxed ISO C mode - CFLAGS="-v" # -v is like gcc -Wall - if test "$enable_debug" != yes; then - CFLAGS="$CFLAGS -O" # any optimization breaks debug - fi - - # Pick the right test-and-set (TAS) code for the Sun compiler. - # We would like to use in-line assembler, but the compiler - # requires *.il files to be on every compile line, making - # the build system too fragile. - case $host_cpu in - sparc) - need_tas=yes - tas_file=sunstudio_sparc.s - ;; - i?86|x86_64) - need_tas=yes - tas_file=sunstudio_x86.s - ;; - esac -fi +CFLAGS_SL="-fPIC" diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck index 17138a7569e4f..d017490a5386a 100755 --- a/src/tools/pginclude/headerscheck +++ b/src/tools/pginclude/headerscheck @@ -114,7 +114,6 @@ do test "$f" = src/include/port/atomics/generic.h && continue test "$f" = src/include/port/atomics/generic-gcc.h && continue test "$f" = src/include/port/atomics/generic-msvc.h && continue - test "$f" = src/include/port/atomics/generic-sunpro.h && continue # sepgsql.h depends on headers that aren't there on most platforms. test "$f" = contrib/sepgsql/sepgsql.h && continue From 2aac62be8cbb870ccf8c5b3fbb8a4e4aa8a14a73 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 12 Sep 2025 07:57:06 +0200 Subject: [PATCH 48/96] Default to log_lock_waits=on If someone is stuck behind a lock for more than a second, that is almost always a problem that is worth a log entry. Author: Laurenz Albe Reviewed-By: Michael Banck Reviewed-By: Robert Haas Reviewed-By: Christoph Berg Reviewed-By: Stephen Frost Discussion: https://postgr.es/m/b8b8502915e50f44deb111bc0b43a99e2733e117.camel%40cybertec.at --- doc/src/sgml/config.sgml | 2 +- src/backend/storage/lmgr/proc.c | 2 +- src/backend/utils/misc/guc_parameters.dat | 2 +- src/backend/utils/misc/postgresql.conf.sample | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 2a3685f474a96..3c33d5d0fbcae 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7929,7 +7929,7 @@ log_line_prefix = '%m [%p] %q%u@%d/%a ' Controls whether a log message is produced when a session waits longer than to acquire a lock. This is useful in determining if lock waits are causing - poor performance. The default is off. + poor performance. The default is on. Only superusers and users with the appropriate SET privilege can change this setting. diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index e9ef0fbfe32cb..96f29aafc391e 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -60,7 +60,7 @@ int LockTimeout = 0; int IdleInTransactionSessionTimeout = 0; int TransactionTimeout = 0; int IdleSessionTimeout = 0; -bool log_lock_waits = false; +bool log_lock_waits = true; /* Pointer to this process's PGPROC struct, if any */ PGPROC *MyProc = NULL; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 0da01627cfec1..6bc6be13d2ad2 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -566,7 +566,7 @@ { name => 'log_lock_waits', type => 'bool', context => 'PGC_SUSET', group => 'LOGGING_WHAT', short_desc => 'Logs long lock waits.', variable => 'log_lock_waits', - boot_val => 'false', + boot_val => 'true', }, { name => 'log_lock_failures', type => 'bool', context => 'PGC_SUSET', group => 'LOGGING_WHAT', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 26c0869356485..c36fcb9ab6105 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -624,7 +624,7 @@ # processes # %% = '%' # e.g. '<%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout +#log_lock_waits = on # log lock waits >= deadlock_timeout #log_lock_failures = off # log lock failures #log_recovery_conflict_waits = off # log standby recovery conflict waits # >= deadlock_timeout From 675ddc4d704f4cde0bc72244263a9efbb0d32cb8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 12 Sep 2025 08:13:05 +0200 Subject: [PATCH 49/96] Improve pgbench definition of yyscan_t It was defining yyscan_t as a macro while the rest of the code uses a typedef with #ifdef guards around it. The latter is also what the flex generated code uses. So it seems best to make it look like those other places for consistency. The old way also had a potential for conflict if some code included multiple headers providing yyscan_t. exprscan.l includes #include "fe_utils/psqlscan_int.h" #include "pgbench.h" and fe_utils/psqlscan_int.h contains #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; #endif which was then followed by pgbench.h #define yyscan_t void * and then the generated code in exprscan.c #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void* yyscan_t; #endif This works, but if the #ifdef guard in psqlscan_int.h is removed, this fails. We want to move toward allowing repeat typedefs, per C11, but for that we need to make sure they are all the same. Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- src/bin/pgbench/pgbench.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index e053c9e2eb63d..f8b7b497d1ee0 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -16,11 +16,14 @@ /* * This file is included outside exprscan.l, in places where we can't see * flex's definition of typedef yyscan_t. Fortunately, it's documented as - * being "void *", so we can use a macro to keep the function declarations + * being "void *", so we can use typedef to keep the function declarations * here looking like the definitions in exprscan.l. exprparse.y and * pgbench.c also use this to be able to declare things as "yyscan_t". */ -#define yyscan_t void * +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void *yyscan_t; +#endif /* * Likewise, we can't see exprparse.y's definition of union YYSTYPE here, From ae0e1be9f2a20f6b64072dcee5b8dd7b9027a8fa Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 12 Sep 2025 08:13:05 +0200 Subject: [PATCH 50/96] Allow redeclaration of typedef yyscan_t This is allowed in C11, so we don't need the workaround guards against it anymore. This effectively reverts commit 382092a0cd2 that put these guards in place. Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- contrib/cube/cubedata.h | 3 --- contrib/seg/segdata.h | 3 --- src/backend/utils/adt/jsonpath_internal.h | 3 --- src/bin/pgbench/pgbench.h | 3 --- src/include/bootstrap/bootstrap.h | 3 --- src/include/fe_utils/psqlscan_int.h | 6 ------ src/include/replication/syncrep.h | 3 --- src/include/replication/walsender_private.h | 3 --- src/pl/plpgsql/src/plpgsql.h | 3 --- 9 files changed, 30 deletions(-) diff --git a/contrib/cube/cubedata.h b/contrib/cube/cubedata.h index ad1e2bd699810..8bfcc6e99a27d 100644 --- a/contrib/cube/cubedata.h +++ b/contrib/cube/cubedata.h @@ -62,10 +62,7 @@ typedef struct NDBOX /* for cubescan.l and cubeparse.y */ /* All grammar constructs return strings */ #define YYSTYPE char * -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif /* in cubescan.l */ extern int cube_yylex(YYSTYPE *yylval_param, yyscan_t yyscanner); diff --git a/contrib/seg/segdata.h b/contrib/seg/segdata.h index 4347c31c28e94..7bc7c83dca309 100644 --- a/contrib/seg/segdata.h +++ b/contrib/seg/segdata.h @@ -16,10 +16,7 @@ extern int significant_digits(const char *s); /* for segscan.l and segparse.y */ union YYSTYPE; -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif /* in segscan.l */ extern int seg_yylex(union YYSTYPE *yylval_param, yyscan_t yyscanner); diff --git a/src/backend/utils/adt/jsonpath_internal.h b/src/backend/utils/adt/jsonpath_internal.h index f78069857d02b..19567aca6f775 100644 --- a/src/backend/utils/adt/jsonpath_internal.h +++ b/src/backend/utils/adt/jsonpath_internal.h @@ -22,10 +22,7 @@ typedef struct JsonPathString int total; } JsonPathString; -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif #include "utils/jsonpath.h" #include "jsonpath_gram.h" diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index f8b7b497d1ee0..d55d30e0ef954 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -20,10 +20,7 @@ * here looking like the definitions in exprscan.l. exprparse.y and * pgbench.c also use this to be able to declare things as "yyscan_t". */ -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif /* * Likewise, we can't see exprparse.y's definition of union YYSTYPE here, diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h index befc4fa1b3d87..5ad347ec290fa 100644 --- a/src/include/bootstrap/bootstrap.h +++ b/src/include/bootstrap/bootstrap.h @@ -56,10 +56,7 @@ extern void boot_get_type_io_data(Oid typid, Oid *typoutput); union YYSTYPE; -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif extern int boot_yyparse(yyscan_t yyscanner); extern int boot_yylex_init(yyscan_t *yyscannerp); diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h index 2a3a9d7c82aaa..a1ebf226cf499 100644 --- a/src/include/fe_utils/psqlscan_int.h +++ b/src/include/fe_utils/psqlscan_int.h @@ -51,14 +51,8 @@ * validity checking; in actual use, this file should always be included * from the body of a flex file, where these symbols are already defined. */ -#ifndef YY_TYPEDEF_YY_BUFFER_STATE -#define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; -#endif -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif /* * We use a stack of flex buffers to handle substitution of psql variables. diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h index 675669a79f7d3..dc2b118b16629 100644 --- a/src/include/replication/syncrep.h +++ b/src/include/replication/syncrep.h @@ -97,10 +97,7 @@ extern void SyncRepUpdateSyncStandbysDefined(void); * in syncrep_gram.y and syncrep_scanner.l */ union YYSTYPE; -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif extern int syncrep_yyparse(SyncRepConfigData **syncrep_parse_result_p, char **syncrep_parse_error_msg_p, yyscan_t yyscanner); extern int syncrep_yylex(union YYSTYPE *yylval_param, char **syncrep_parse_error_msg_p, yyscan_t yyscanner); extern void syncrep_yyerror(SyncRepConfigData **syncrep_parse_result_p, char **syncrep_parse_error_msg_p, yyscan_t yyscanner, const char *str); diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index e98701038f506..384b8a78b9462 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -141,10 +141,7 @@ extern void WalSndSetState(WalSndState state); * repl_scanner.l */ union YYSTYPE; -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif extern int replication_yyparse(Node **replication_parse_result_p, yyscan_t yyscanner); extern int replication_yylex(union YYSTYPE *yylval_param, yyscan_t yyscanner); pg_noreturn extern void replication_yyerror(Node **replication_parse_result_p, yyscan_t yyscanner, const char *message); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 41e52b8ce7183..5f193a3718399 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -1307,10 +1307,7 @@ extern void plpgsql_dumptree(PLpgSQL_function *func); */ union YYSTYPE; #define YYLTYPE int -#ifndef YY_TYPEDEF_YY_SCANNER_T -#define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; -#endif extern int plpgsql_yylex(union YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner); extern int plpgsql_token_length(yyscan_t yyscanner); extern void plpgsql_push_back_token(int token, union YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner); From 20d541a200e9dfed8affef9e798ff35ca0f30b8e Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 12 Sep 2025 10:18:31 -0400 Subject: [PATCH 51/96] ci: openbsd: Increase RAM disk's size Its size was ~3.8GB before, which sometimes was not enough. OpenBSD CI task often were failing due to no space left on device. Increase the RAM disk size to ~4.6 GB. Author: Nazir Bilal Yavuz Discussion: https://postgr.es/m/CAN55FZ2XVVPJRJmGB2DsL3gOrOinWh=HWvj6GO1cHzJ=6LwTag@mail.gmail.com Backpatch-through: 18, where openbsd was added to CI --- src/tools/ci/gcp_ram_disk.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/ci/gcp_ram_disk.sh b/src/tools/ci/gcp_ram_disk.sh index d48634512ac28..18dbb2037f5dc 100755 --- a/src/tools/ci/gcp_ram_disk.sh +++ b/src/tools/ci/gcp_ram_disk.sh @@ -15,7 +15,12 @@ case "`uname`" in umount /dev/sd0j # unused /usr/obj partition printf "m j\n\n\nswap\nw\nq\n" | disklabel -E sd0 swapon /dev/sd0j - mount -t mfs -o rw,noatime,nodev,-s=8000000 swap $CIRRUS_WORKING_DIR + # Remove the per-process data segment limit so that mount_mfs can allocate + # large memory filesystems. Without this, mount_mfs mmap() may fail with + # "Cannot allocate memory" if the requested size exceeds the current + # datasize limit. + ulimit -d unlimited + mount -t mfs -o rw,noatime,nodev,-s=10000000 swap $CIRRUS_WORKING_DIR ;; esac From 7dcea51c2a4dcf7c512bbd4f618d1d3620f9d3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 12 Sep 2025 18:47:25 +0200 Subject: [PATCH 52/96] Avoid unexpected changes of CurrentResourceOwner and CurrentMemoryContext Users of logical decoding can encounter an unexpected change of CurrentResourceOwner and CurrentMemoryContext. The problem is that, unlike other call sites of RollbackAndReleaseCurrentSubTransaction(), in reorderbuffer.c we fail to restore the original values of these global variables after being clobbered by subtransaction abort. This patch saves the values prior to the call and restores them eventually. In addition, logical.c and logicalfuncs.c had a hack to restore resource owner, presumably because of lack of this restore. Remove that. Instead, because the test coverage here is not very consistent, add an Assert() to ensure that the resowner is kept identical; this would make it easy to detect other cases of bugs were we fail to restore resowner properly. This could be removed later. This is arguably an old bug, but there appears to be no reason to backpatch it and it's risky to do so, so refrain for now. Author: Antonin Houska Reported-by: Mihail Nikalayeu Reviewed-by: Euler Taveira Discussion: https://postgr.es/m/119497.1756892972@localhost --- src/backend/replication/logical/logical.c | 19 +++++++++++-------- .../replication/logical/logicalfuncs.c | 19 +++++++++++-------- .../replication/logical/reorderbuffer.c | 15 +++++++++++++++ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 7e363a7c05b4f..c68c0481f427a 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -2082,7 +2082,7 @@ LogicalSlotAdvanceAndCheckSnapState(XLogRecPtr moveto, bool *found_consistent_snapshot) { LogicalDecodingContext *ctx; - ResourceOwner old_resowner = CurrentResourceOwner; + ResourceOwner old_resowner PG_USED_FOR_ASSERTS_ONLY = CurrentResourceOwner; XLogRecPtr retlsn; Assert(moveto != InvalidXLogRecPtr); @@ -2141,21 +2141,24 @@ LogicalSlotAdvanceAndCheckSnapState(XLogRecPtr moveto, * might still have critical updates to do. */ if (record) + { LogicalDecodingProcessRecord(ctx, ctx->reader); + /* + * We used to have bugs where logical decoding would fail to + * preserve the resource owner. That's important here, so + * verify that that doesn't happen anymore. XXX this could be + * removed once it's been battle-tested. + */ + Assert(CurrentResourceOwner == old_resowner); + } + CHECK_FOR_INTERRUPTS(); } if (found_consistent_snapshot && DecodingContextReady(ctx)) *found_consistent_snapshot = true; - /* - * Logical decoding could have clobbered CurrentResourceOwner during - * transaction management, so restore the executor's value. (This is - * a kluge, but it's not worth cleaning up right now.) - */ - CurrentResourceOwner = old_resowner; - if (ctx->reader->EndRecPtr != InvalidXLogRecPtr) { LogicalConfirmReceivedLocation(moveto); diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index ca53caac2f2f5..25f890ddeedac 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -107,7 +107,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin XLogRecPtr end_of_wal; XLogRecPtr wait_for_wal_lsn; LogicalDecodingContext *ctx; - ResourceOwner old_resowner = CurrentResourceOwner; + ResourceOwner old_resowner PG_USED_FOR_ASSERTS_ONLY = CurrentResourceOwner; ArrayType *arr; Size ndim; List *options = NIL; @@ -263,8 +263,18 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin * store the description into our tuplestore. */ if (record != NULL) + { LogicalDecodingProcessRecord(ctx, ctx->reader); + /* + * We used to have bugs where logical decoding would fail to + * preserve the resource owner. Verify that that doesn't + * happen anymore. XXX this could be removed once it's been + * battle-tested. + */ + Assert(CurrentResourceOwner == old_resowner); + } + /* check limits */ if (upto_lsn != InvalidXLogRecPtr && upto_lsn <= ctx->reader->EndRecPtr) @@ -275,13 +285,6 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CHECK_FOR_INTERRUPTS(); } - /* - * Logical decoding could have clobbered CurrentResourceOwner during - * transaction management, so restore the executor's value. (This is - * a kluge, but it's not worth cleaning up right now.) - */ - CurrentResourceOwner = old_resowner; - /* * Next time, start where we left off. (Hunting things, the family * business..) diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 34cf05668ae84..4736f993c3743 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -2215,6 +2215,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, { bool using_subtxn; MemoryContext ccxt = CurrentMemoryContext; + ResourceOwner cowner = CurrentResourceOwner; ReorderBufferIterTXNState *volatile iterstate = NULL; volatile XLogRecPtr prev_lsn = InvalidXLogRecPtr; ReorderBufferChange *volatile specinsert = NULL; @@ -2692,7 +2693,11 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, } if (using_subtxn) + { RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(ccxt); + CurrentResourceOwner = cowner; + } /* * We are here due to one of the four reasons: 1. Decoding an @@ -2751,7 +2756,11 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, } if (using_subtxn) + { RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(ccxt); + CurrentResourceOwner = cowner; + } /* * The error code ERRCODE_TRANSACTION_ROLLBACK indicates a concurrent @@ -3244,6 +3253,8 @@ ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations, SharedInvalidationMessage *invalidations) { bool use_subtxn = IsTransactionOrTransactionBlock(); + MemoryContext ccxt = CurrentMemoryContext; + ResourceOwner cowner = CurrentResourceOwner; int i; if (use_subtxn) @@ -3262,7 +3273,11 @@ ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations, LocalExecuteInvalidationMessage(&invalidations[i]); if (use_subtxn) + { RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(ccxt); + CurrentResourceOwner = cowner; + } } /* From 796962922e6938fdad4dbf810fb2a5dfcfc5f45a Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Fri, 12 Sep 2025 13:23:00 -0400 Subject: [PATCH 53/96] Always commute strategy when preprocessing DESC keys. A recently added nbtree preprocessing step failed to account for the fact that DESC columns already had their B-Tree strategy number commuted at this point in preprocessing. As a result, preprocessing could output a set of scan keys where one or more keys had the correct strategy number, but used the wrong comparison routine. To fix, make the faulty code path that looks up a more restrictive replacement operator/comparison routine commute its requested inequality strategy (while outputting the transformed strategy number as before). This makes the final transformed scan key comport with the approach preprocessing has always used to deal with DESC columns (which is described by comments above _bt_fix_scankey_strategy). Oversight in commit commit b3f1a13f, which made nbtree preprocessing perform transformations on skip array inequalities that can reduce the total number of index searches. Author: Peter Geoghegan Reported-By: Natalya Aksman Discussion: https://postgr.es/m/19049-b7df801e71de41b2@postgresql.org Backpatch-through: 18 --- src/backend/access/nbtree/nbtpreprocesskeys.c | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/backend/access/nbtree/nbtpreprocesskeys.c b/src/backend/access/nbtree/nbtpreprocesskeys.c index 936b93f157a8b..07a3ff11a0b87 100644 --- a/src/backend/access/nbtree/nbtpreprocesskeys.c +++ b/src/backend/access/nbtree/nbtpreprocesskeys.c @@ -1412,6 +1412,7 @@ _bt_skiparray_strat_decrement(IndexScanDesc scan, ScanKey arraysk, Datum orig_sk_argument = high_compare->sk_argument, new_sk_argument; bool uflow; + int16 lookupstrat; Assert(high_compare->sk_strategy == BTLessStrategyNumber); @@ -1433,9 +1434,14 @@ _bt_skiparray_strat_decrement(IndexScanDesc scan, ScanKey arraysk, return; } - /* Look up <= operator (might fail) */ - leop = get_opfamily_member(opfamily, opcintype, opcintype, - BTLessEqualStrategyNumber); + /* + * Look up <= operator (might fail), accounting for the fact that a + * high_compare on a DESC column already had its strategy commuted + */ + lookupstrat = BTLessEqualStrategyNumber; + if (high_compare->sk_flags & SK_BT_DESC) + lookupstrat = BTGreaterEqualStrategyNumber; /* commute this too */ + leop = get_opfamily_member(opfamily, opcintype, opcintype, lookupstrat); if (!OidIsValid(leop)) return; cmp_proc = get_opcode(leop); @@ -1464,6 +1470,7 @@ _bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk, Datum orig_sk_argument = low_compare->sk_argument, new_sk_argument; bool oflow; + int16 lookupstrat; Assert(low_compare->sk_strategy == BTGreaterStrategyNumber); @@ -1485,9 +1492,14 @@ _bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk, return; } - /* Look up >= operator (might fail) */ - geop = get_opfamily_member(opfamily, opcintype, opcintype, - BTGreaterEqualStrategyNumber); + /* + * Look up >= operator (might fail), accounting for the fact that a + * low_compare on a DESC column already had its strategy commuted + */ + lookupstrat = BTGreaterEqualStrategyNumber; + if (low_compare->sk_flags & SK_BT_DESC) + lookupstrat = BTLessEqualStrategyNumber; /* commute this too */ + geop = get_opfamily_member(opfamily, opcintype, opcintype, lookupstrat); if (!OidIsValid(geop)) return; cmp_proc = get_opcode(geop); From 4adb0380b9bff5ec6424a9e87f76f8974b025367 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Fri, 12 Sep 2025 12:44:14 -0700 Subject: [PATCH 54/96] Replace tests of ALTER DATABASE RESET TABLESPACE. This unblocks rejection of that syntax. One copy was a misspelling of "SET TABLESPACE pg_default" that instead made no persistent changes. The other copy just needed to populate a DATABASEOID syscache entry. This slightly raises database.sql test coverage of catcache.c, while dbcommands.c coverage remains the same. Reported-by: Tom Lane Reviewed-by: Tom Lane Discussion: https://postgr.es/m/1802710.1757608564@sss.pgh.pa.us --- src/test/regress/expected/database.out | 4 ++-- src/test/regress/sql/database.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/regress/expected/database.out b/src/test/regress/expected/database.out index 4cbdbdf84d0c5..6b879b0f62a75 100644 --- a/src/test/regress/expected/database.out +++ b/src/test/regress/expected/database.out @@ -2,7 +2,7 @@ CREATE DATABASE regression_tbd ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; ALTER DATABASE regression_tbd RENAME TO regression_utf8; ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace; -ALTER DATABASE regression_utf8 RESET TABLESPACE; +ALTER DATABASE regression_utf8 SET TABLESPACE pg_default; ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123; -- Test PgDatabaseToastTable. Doing this with GRANT would be slow. BEGIN; @@ -10,7 +10,7 @@ UPDATE pg_database SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int]) WHERE datname = 'regression_utf8'; -- load catcache entry, if nothing else does -ALTER DATABASE regression_utf8 RESET TABLESPACE; +ALTER DATABASE regression_utf8 RENAME TO regression_rename_rolled_back; ROLLBACK; CREATE ROLE regress_datdba_before; CREATE ROLE regress_datdba_after; diff --git a/src/test/regress/sql/database.sql b/src/test/regress/sql/database.sql index 46ad2634781ea..4ef361272911e 100644 --- a/src/test/regress/sql/database.sql +++ b/src/test/regress/sql/database.sql @@ -2,7 +2,7 @@ CREATE DATABASE regression_tbd ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; ALTER DATABASE regression_tbd RENAME TO regression_utf8; ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace; -ALTER DATABASE regression_utf8 RESET TABLESPACE; +ALTER DATABASE regression_utf8 SET TABLESPACE pg_default; ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123; -- Test PgDatabaseToastTable. Doing this with GRANT would be slow. @@ -11,7 +11,7 @@ UPDATE pg_database SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int]) WHERE datname = 'regression_utf8'; -- load catcache entry, if nothing else does -ALTER DATABASE regression_utf8 RESET TABLESPACE; +ALTER DATABASE regression_utf8 RENAME TO regression_rename_rolled_back; ROLLBACK; CREATE ROLE regress_datdba_before; From f14ea34d6e563374cd71b4e7b91cf8d2f60aabb3 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 12 Sep 2025 17:43:15 -0400 Subject: [PATCH 55/96] Fix oversights in pg_event_trigger_dropped_objects() fixes. Commit a0b99fc12 caused pg_event_trigger_dropped_objects() to not fill the object_name field for schemas, which it should have; and caused it to fill the object_name field for default values, which it should not have. In addition, triggers and RLS policies really should behave the same way as we're making column defaults do; that is, they should have is_temporary = true if they belong to a temporary table. Fix those things, and upgrade event_trigger.sql's woefully inadequate test coverage of these secondary output columns. As before, back-patch only to v15. Reported-by: Sergey Shinderuk Author: Tom Lane Discussion: https://postgr.es/m/bd7b4651-1c26-4d30-832b-f942fabcb145@postgrespro.ru Backpatch-through: 15 --- src/backend/commands/event_trigger.c | 87 ++++++++- src/test/regress/expected/event_trigger.out | 188 ++++++++++++-------- src/test/regress/sql/event_trigger.sql | 55 ++++-- 3 files changed, 244 insertions(+), 86 deletions(-) diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index fcdcba009d4e3..f34868da5ab94 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -30,6 +30,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_parameter_acl.h" +#include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -1302,6 +1303,7 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no MemoryContextSwitchTo(oldcxt); return; } + obj->objname = get_namespace_name(object->objectId); } else if (object->classId == AttrDefaultRelationId) { @@ -1311,7 +1313,6 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no colobject = GetAttrDefaultColumnAddress(object->objectId); if (OidIsValid(colobject.objectId)) { - colobject.objectSubId = 0; /* convert to table reference */ if (!obtain_object_name_namespace(&colobject, obj)) { pfree(obj); @@ -1320,6 +1321,90 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no } } } + else if (object->classId == TriggerRelationId) + { + /* Similarly, a trigger is temp if its table is temp */ + /* Sadly, there's no lsyscache.c support for trigger objects */ + Relation pg_trigger_rel; + ScanKeyData skey[1]; + SysScanDesc sscan; + HeapTuple tuple; + Oid relid; + + /* Fetch the trigger's table OID the hard way */ + pg_trigger_rel = table_open(TriggerRelationId, AccessShareLock); + ScanKeyInit(&skey[0], + Anum_pg_trigger_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + sscan = systable_beginscan(pg_trigger_rel, TriggerOidIndexId, true, + NULL, 1, skey); + tuple = systable_getnext(sscan); + if (HeapTupleIsValid(tuple)) + relid = ((Form_pg_trigger) GETSTRUCT(tuple))->tgrelid; + else + relid = InvalidOid; /* shouldn't happen */ + systable_endscan(sscan); + table_close(pg_trigger_rel, AccessShareLock); + /* Do nothing if we didn't find the trigger */ + if (OidIsValid(relid)) + { + ObjectAddress relobject; + + relobject.classId = RelationRelationId; + relobject.objectId = relid; + /* Arbitrarily set objectSubId nonzero so as not to fill objname */ + relobject.objectSubId = 1; + if (!obtain_object_name_namespace(&relobject, obj)) + { + pfree(obj); + MemoryContextSwitchTo(oldcxt); + return; + } + } + } + else if (object->classId == PolicyRelationId) + { + /* Similarly, a policy is temp if its table is temp */ + /* Sadly, there's no lsyscache.c support for policy objects */ + Relation pg_policy_rel; + ScanKeyData skey[1]; + SysScanDesc sscan; + HeapTuple tuple; + Oid relid; + + /* Fetch the policy's table OID the hard way */ + pg_policy_rel = table_open(PolicyRelationId, AccessShareLock); + ScanKeyInit(&skey[0], + Anum_pg_policy_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true, + NULL, 1, skey); + tuple = systable_getnext(sscan); + if (HeapTupleIsValid(tuple)) + relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; + else + relid = InvalidOid; /* shouldn't happen */ + systable_endscan(sscan); + table_close(pg_policy_rel, AccessShareLock); + /* Do nothing if we didn't find the policy */ + if (OidIsValid(relid)) + { + ObjectAddress relobject; + + relobject.classId = RelationRelationId; + relobject.objectId = relid; + /* Arbitrarily set objectSubId nonzero so as not to fill objname */ + relobject.objectSubId = 1; + if (!obtain_object_name_namespace(&relobject, obj)) + { + pfree(obj); + MemoryContextSwitchTo(oldcxt); + return; + } + } + } else { /* Generic handling for all other object classes */ diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 0e090cbc37500..16e4530708cc9 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -228,9 +228,15 @@ INSERT INTO undroppable_objs VALUES ('table', 'schema_one.table_three'), ('table', 'audit_tbls.schema_two_table_three'); CREATE TABLE dropped_objects ( - type text, - schema text, - object text + object_type text, + schema_name text, + object_name text, + object_identity text, + address_names text[], + address_args text[], + is_temporary bool, + original bool, + normal bool ); -- This tests errors raised within event triggers; the one in audit_tbls -- uses 2nd-level recursive invocation via test_evtrig_dropped_objects(). @@ -268,8 +274,12 @@ BEGIN END IF; INSERT INTO dropped_objects - (type, schema, object) VALUES - (obj.object_type, obj.schema_name, obj.object_identity); + (object_type, schema_name, object_name, + object_identity, address_names, address_args, + is_temporary, original, normal) VALUES + (obj.object_type, obj.schema_name, obj.object_name, + obj.object_identity, obj.address_names, obj.address_args, + obj.is_temporary, obj.original, obj.normal); END LOOP; END $$; @@ -325,42 +335,44 @@ NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping NOTICE: table "schema_one_table_one" does not exist, skipping NOTICE: table "schema_one_table two" does not exist, skipping NOTICE: table "schema_one_table_three" does not exist, skipping -SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; - type | schema | object ---------------+------------+------------------------------------- - table column | schema_one | schema_one.table_one.a - schema | | schema_two - table | schema_two | schema_two.table_two - type | schema_two | schema_two.table_two - type | schema_two | schema_two.table_two[] - table | audit_tbls | audit_tbls.schema_two_table_three - type | audit_tbls | audit_tbls.schema_two_table_three - type | audit_tbls | audit_tbls.schema_two_table_three[] - table | schema_two | schema_two.table_three - type | schema_two | schema_two.table_three - type | schema_two | schema_two.table_three[] - function | schema_two | schema_two.add(integer,integer) - aggregate | schema_two | schema_two.newton(integer) - schema | | schema_one - table | schema_one | schema_one.table_one - type | schema_one | schema_one.table_one - type | schema_one | schema_one.table_one[] - table | schema_one | schema_one."table two" - type | schema_one | schema_one."table two" - type | schema_one | schema_one."table two"[] - table | schema_one | schema_one.table_three - type | schema_one | schema_one.table_three - type | schema_one | schema_one.table_three[] +-- exclude TOAST objects because they have unstable names +SELECT * FROM dropped_objects + WHERE schema_name IS NULL OR schema_name <> 'pg_toast'; + object_type | schema_name | object_name | object_identity | address_names | address_args | is_temporary | original | normal +--------------+-------------+-------------------------+-------------------------------------+---------------------------------------+-------------------+--------------+----------+-------- + table column | schema_one | | schema_one.table_one.a | {schema_one,table_one,a} | {} | f | t | f + schema | | schema_two | schema_two | {schema_two} | {} | f | t | f + table | schema_two | table_two | schema_two.table_two | {schema_two,table_two} | {} | f | f | t + type | schema_two | table_two | schema_two.table_two | {schema_two.table_two} | {} | f | f | f + type | schema_two | _table_two | schema_two.table_two[] | {schema_two.table_two[]} | {} | f | f | f + table | audit_tbls | schema_two_table_three | audit_tbls.schema_two_table_three | {audit_tbls,schema_two_table_three} | {} | f | t | f + type | audit_tbls | schema_two_table_three | audit_tbls.schema_two_table_three | {audit_tbls.schema_two_table_three} | {} | f | f | f + type | audit_tbls | _schema_two_table_three | audit_tbls.schema_two_table_three[] | {audit_tbls.schema_two_table_three[]} | {} | f | f | f + table | schema_two | table_three | schema_two.table_three | {schema_two,table_three} | {} | f | f | t + type | schema_two | table_three | schema_two.table_three | {schema_two.table_three} | {} | f | f | f + type | schema_two | _table_three | schema_two.table_three[] | {schema_two.table_three[]} | {} | f | f | f + function | schema_two | | schema_two.add(integer,integer) | {schema_two,add} | {integer,integer} | f | f | t + aggregate | schema_two | | schema_two.newton(integer) | {schema_two,newton} | {integer} | f | f | t + schema | | schema_one | schema_one | {schema_one} | {} | f | t | f + table | schema_one | table_one | schema_one.table_one | {schema_one,table_one} | {} | f | f | t + type | schema_one | table_one | schema_one.table_one | {schema_one.table_one} | {} | f | f | f + type | schema_one | _table_one | schema_one.table_one[] | {schema_one.table_one[]} | {} | f | f | f + table | schema_one | table two | schema_one."table two" | {schema_one,"table two"} | {} | f | f | t + type | schema_one | table two | schema_one."table two" | {"schema_one.\"table two\""} | {} | f | f | f + type | schema_one | _table two | schema_one."table two"[] | {"schema_one.\"table two\"[]"} | {} | f | f | f + table | schema_one | table_three | schema_one.table_three | {schema_one,table_three} | {} | f | f | t + type | schema_one | table_three | schema_one.table_three | {schema_one.table_three} | {} | f | f | f + type | schema_one | _table_three | schema_one.table_three[] | {schema_one.table_three[]} | {} | f | f | f (23 rows) DROP OWNED BY regress_evt_user; NOTICE: schema "audit_tbls" does not exist, skipping -SELECT * FROM dropped_objects WHERE type = 'schema'; - type | schema | object ---------+--------+------------ - schema | | schema_two - schema | | schema_one - schema | | audit_tbls +SELECT * FROM dropped_objects WHERE object_type = 'schema'; + object_type | schema_name | object_name | object_identity | address_names | address_args | is_temporary | original | normal +-------------+-------------+-------------+-----------------+---------------+--------------+--------------+----------+-------- + schema | | schema_two | schema_two | {schema_two} | {} | f | t | f + schema | | schema_one | schema_one | {schema_one} | {} | f | t | f + schema | | audit_tbls | audit_tbls | {audit_tbls} | {} | f | t | f (3 rows) DROP ROLE regress_evt_user; @@ -378,9 +390,10 @@ BEGIN IF NOT r.normal AND NOT r.original THEN CONTINUE; END IF; - RAISE NOTICE 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%', + RAISE NOTICE 'NORMAL: orig=% normal=% istemp=% type=% identity=% schema=% name=% addr=% args=%', r.original, r.normal, r.is_temporary, r.object_type, - r.object_identity, r.address_names, r.address_args; + r.object_identity, r.schema_name, r.object_name, + r.address_names, r.address_args; END LOOP; END; $$; CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop @@ -436,18 +449,18 @@ CREATE TABLE evttrig.part_15_20 PARTITION OF evttrig.part_10_20 (id) FOR VALUES FROM (15) TO (20); NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_15_20 ALTER TABLE evttrig.two DROP COLUMN col_c; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.two.col_c name={evttrig,two,col_c} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table constraint identity=two_col_c_check on evttrig.two name={evttrig,two,two_col_c_check} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.two.col_c schema=evttrig name= addr={evttrig,two,col_c} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table constraint identity=two_col_c_check on evttrig.two schema=evttrig name= addr={evttrig,two,two_col_c_check} args={} NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two ALTER TABLE evttrig.one ALTER COLUMN col_b DROP DEFAULT; -NOTICE: NORMAL: orig=t normal=f istemp=f type=default value identity=for evttrig.one.col_b name={evttrig,one,col_b} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=default value identity=for evttrig.one.col_b schema=evttrig name= addr={evttrig,one,col_b} args={} NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one ALTER TABLE evttrig.one DROP CONSTRAINT one_pkey; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table constraint identity=one_pkey on evttrig.one name={evttrig,one,one_pkey} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=table constraint identity=one_pkey on evttrig.one schema=evttrig name= addr={evttrig,one,one_pkey} args={} NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one ALTER TABLE evttrig.one DROP COLUMN col_c; -NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.one.col_c name={evttrig,one,col_c} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_c name={evttrig,one,col_c} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.one.col_c schema=evttrig name= addr={evttrig,one,col_c} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_c schema=evttrig name= addr={evttrig,one,col_c} args={} NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one ALTER TABLE evttrig.id ALTER COLUMN col_d SET DATA TYPE bigint; NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq @@ -456,26 +469,26 @@ ALTER TABLE evttrig.id ALTER COLUMN col_d DROP IDENTITY, ALTER COLUMN col_d SET DATA TYPE int; NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.id DROP INDEX evttrig.one_idx; -NOTICE: NORMAL: orig=t normal=f istemp=f type=index identity=evttrig.one_idx name={evttrig,one_idx} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=index identity=evttrig.one_idx schema=evttrig name=one_idx addr={evttrig,one_idx} args={} DROP SCHEMA evttrig CASCADE; NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table evttrig.one drop cascades to table evttrig.two drop cascades to table evttrig.id drop cascades to table evttrig.parted -NOTICE: NORMAL: orig=t normal=f istemp=f type=schema identity=evttrig name={evttrig} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.one name={evttrig,one} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=sequence identity=evttrig.one_col_a_seq name={evttrig,one_col_a_seq} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_a name={evttrig,one,col_a} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.two name={evttrig,two} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.id name={evttrig,id} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.parted name={evttrig,parted} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_1_10 name={evttrig,part_1_10} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_20 name={evttrig,part_10_20} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_15 name={evttrig,part_10_15} args={} -NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_15_20 name={evttrig,part_15_20} args={} +NOTICE: NORMAL: orig=t normal=f istemp=f type=schema identity=evttrig schema= name=evttrig addr={evttrig} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.one schema=evttrig name=one addr={evttrig,one} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=sequence identity=evttrig.one_col_a_seq schema=evttrig name=one_col_a_seq addr={evttrig,one_col_a_seq} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_a schema=evttrig name= addr={evttrig,one,col_a} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.two schema=evttrig name=two addr={evttrig,two} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.id schema=evttrig name=id addr={evttrig,id} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.parted schema=evttrig name=parted addr={evttrig,parted} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_1_10 schema=evttrig name=part_1_10 addr={evttrig,part_1_10} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_20 schema=evttrig name=part_10_20 addr={evttrig,part_10_20} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_15 schema=evttrig name=part_10_15 addr={evttrig,part_10_15} args={} +NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_15_20 schema=evttrig name=part_15_20 addr={evttrig,part_15_20} args={} DROP TABLE a_temp_tbl; -NOTICE: NORMAL: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={} +NOTICE: NORMAL: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl schema=pg_temp name=a_temp_tbl addr={pg_temp,a_temp_tbl} args={} -- check unfiltered results, too CREATE OR REPLACE FUNCTION event_trigger_report_dropped() RETURNS event_trigger @@ -485,34 +498,61 @@ DECLARE r record; BEGIN FOR r IN SELECT * from pg_event_trigger_dropped_objects() LOOP - RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% name=% args=%', + RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% schema=% name=% addr=% args=%', r.original, r.normal, r.is_temporary, r.object_type, - r.object_identity, r.address_names, r.address_args; + r.object_identity, r.schema_name, r.object_name, + r.address_names, r.address_args; END LOOP; END; $$; NOTICE: END: command_tag=CREATE FUNCTION type=function identity=public.event_trigger_report_dropped() +CREATE FUNCTION event_trigger_dummy_trigger() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RETURN new; +END; $$; +NOTICE: END: command_tag=CREATE FUNCTION type=function identity=public.event_trigger_dummy_trigger() CREATE TABLE evtrg_nontemp_table (f1 int primary key, f2 int default 42); NOTICE: END: command_tag=CREATE TABLE type=table identity=public.evtrg_nontemp_table NOTICE: END: command_tag=CREATE INDEX type=index identity=public.evtrg_nontemp_table_pkey +CREATE TRIGGER evtrg_nontemp_trig + BEFORE INSERT ON evtrg_nontemp_table + EXECUTE FUNCTION event_trigger_dummy_trigger(); +NOTICE: END: command_tag=CREATE TRIGGER type=trigger identity=evtrg_nontemp_trig on public.evtrg_nontemp_table +CREATE POLICY evtrg_nontemp_pol ON evtrg_nontemp_table USING (f2 > 0); +NOTICE: END: command_tag=CREATE POLICY type=policy identity=evtrg_nontemp_pol on public.evtrg_nontemp_table DROP TABLE evtrg_nontemp_table; -NOTICE: DROP: orig=t normal=f istemp=f type=table identity=public.evtrg_nontemp_table name={public,evtrg_nontemp_table} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table name={public.evtrg_nontemp_table} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table[] name={public.evtrg_nontemp_table[]} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=default value identity=for public.evtrg_nontemp_table.f2 name={public,evtrg_nontemp_table,f2} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_f1_not_null on public.evtrg_nontemp_table name={public,evtrg_nontemp_table,evtrg_nontemp_table_f1_not_null} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_pkey on public.evtrg_nontemp_table name={public,evtrg_nontemp_table,evtrg_nontemp_table_pkey} args={} -NOTICE: DROP: orig=f normal=f istemp=f type=index identity=public.evtrg_nontemp_table_pkey name={public,evtrg_nontemp_table_pkey} args={} +NOTICE: DROP: orig=t normal=f istemp=f type=table identity=public.evtrg_nontemp_table schema=public name=evtrg_nontemp_table addr={public,evtrg_nontemp_table} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table schema=public name=evtrg_nontemp_table addr={public.evtrg_nontemp_table} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=type identity=public.evtrg_nontemp_table[] schema=public name=_evtrg_nontemp_table addr={public.evtrg_nontemp_table[]} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=default value identity=for public.evtrg_nontemp_table.f2 schema=public name= addr={public,evtrg_nontemp_table,f2} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_f1_not_null on public.evtrg_nontemp_table schema=public name= addr={public,evtrg_nontemp_table,evtrg_nontemp_table_f1_not_null} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=table constraint identity=evtrg_nontemp_table_pkey on public.evtrg_nontemp_table schema=public name= addr={public,evtrg_nontemp_table,evtrg_nontemp_table_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=index identity=public.evtrg_nontemp_table_pkey schema=public name=evtrg_nontemp_table_pkey addr={public,evtrg_nontemp_table_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=f type=trigger identity=evtrg_nontemp_trig on public.evtrg_nontemp_table schema=public name= addr={public,evtrg_nontemp_table,evtrg_nontemp_trig} args={} +NOTICE: DROP: orig=f normal=t istemp=f type=policy identity=evtrg_nontemp_pol on public.evtrg_nontemp_table schema=public name= addr={public,evtrg_nontemp_table,evtrg_nontemp_pol} args={} CREATE TEMP TABLE a_temp_tbl (f1 int primary key, f2 int default 42); NOTICE: END: command_tag=CREATE TABLE type=table identity=pg_temp.a_temp_tbl NOTICE: END: command_tag=CREATE INDEX type=index identity=pg_temp.a_temp_tbl_pkey +CREATE TRIGGER a_temp_trig + BEFORE INSERT ON a_temp_tbl + EXECUTE FUNCTION event_trigger_dummy_trigger(); +NOTICE: END: command_tag=CREATE TRIGGER type=trigger identity=a_temp_trig on pg_temp.a_temp_tbl +CREATE POLICY a_temp_pol ON a_temp_tbl USING (f2 > 0); +NOTICE: END: command_tag=CREATE POLICY type=policy identity=a_temp_pol on pg_temp.a_temp_tbl DROP TABLE a_temp_tbl; -NOTICE: DROP: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl name={pg_temp.a_temp_tbl} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl[] name={pg_temp.a_temp_tbl[]} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=default value identity=for pg_temp.a_temp_tbl.f2 name={pg_temp,a_temp_tbl,f2} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_f1_not_null on pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl,a_temp_tbl_f1_not_null} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_pkey on pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl,a_temp_tbl_pkey} args={} -NOTICE: DROP: orig=f normal=f istemp=t type=index identity=pg_temp.a_temp_tbl_pkey name={pg_temp,a_temp_tbl_pkey} args={} +NOTICE: DROP: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl schema=pg_temp name=a_temp_tbl addr={pg_temp,a_temp_tbl} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl schema=pg_temp name=a_temp_tbl addr={pg_temp.a_temp_tbl} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=type identity=pg_temp.a_temp_tbl[] schema=pg_temp name=_a_temp_tbl addr={pg_temp.a_temp_tbl[]} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=default value identity=for pg_temp.a_temp_tbl.f2 schema=pg_temp name= addr={pg_temp,a_temp_tbl,f2} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_f1_not_null on pg_temp.a_temp_tbl schema=pg_temp name= addr={pg_temp,a_temp_tbl,a_temp_tbl_f1_not_null} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=table constraint identity=a_temp_tbl_pkey on pg_temp.a_temp_tbl schema=pg_temp name= addr={pg_temp,a_temp_tbl,a_temp_tbl_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=index identity=pg_temp.a_temp_tbl_pkey schema=pg_temp name=a_temp_tbl_pkey addr={pg_temp,a_temp_tbl_pkey} args={} +NOTICE: DROP: orig=f normal=f istemp=t type=trigger identity=a_temp_trig on pg_temp.a_temp_tbl schema=pg_temp name= addr={pg_temp,a_temp_tbl,a_temp_trig} args={} +NOTICE: DROP: orig=f normal=t istemp=t type=policy identity=a_temp_pol on pg_temp.a_temp_tbl schema=pg_temp name= addr={pg_temp,a_temp_tbl,a_temp_pol} args={} +DROP FUNCTION event_trigger_dummy_trigger(); +NOTICE: DROP: orig=t normal=f istemp=f type=function identity=public.event_trigger_dummy_trigger() schema=public name= addr={public,event_trigger_dummy_trigger} args={} -- CREATE OPERATOR CLASS without FAMILY clause should report -- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int; diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index ef5978b9697aa..c613c0cfd439b 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -202,9 +202,15 @@ INSERT INTO undroppable_objs VALUES ('table', 'audit_tbls.schema_two_table_three'); CREATE TABLE dropped_objects ( - type text, - schema text, - object text + object_type text, + schema_name text, + object_name text, + object_identity text, + address_names text[], + address_args text[], + is_temporary bool, + original bool, + normal bool ); -- This tests errors raised within event triggers; the one in audit_tbls @@ -245,8 +251,12 @@ BEGIN END IF; INSERT INTO dropped_objects - (type, schema, object) VALUES - (obj.object_type, obj.schema_name, obj.object_identity); + (object_type, schema_name, object_name, + object_identity, address_names, address_args, + is_temporary, original, normal) VALUES + (obj.object_type, obj.schema_name, obj.object_name, + obj.object_identity, obj.address_names, obj.address_args, + obj.is_temporary, obj.original, obj.normal); END LOOP; END $$; @@ -263,10 +273,12 @@ DROP SCHEMA schema_one, schema_two CASCADE; DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three'; DROP SCHEMA schema_one, schema_two CASCADE; -SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; +-- exclude TOAST objects because they have unstable names +SELECT * FROM dropped_objects + WHERE schema_name IS NULL OR schema_name <> 'pg_toast'; DROP OWNED BY regress_evt_user; -SELECT * FROM dropped_objects WHERE type = 'schema'; +SELECT * FROM dropped_objects WHERE object_type = 'schema'; DROP ROLE regress_evt_user; @@ -285,9 +297,10 @@ BEGIN IF NOT r.normal AND NOT r.original THEN CONTINUE; END IF; - RAISE NOTICE 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%', + RAISE NOTICE 'NORMAL: orig=% normal=% istemp=% type=% identity=% schema=% name=% addr=% args=%', r.original, r.normal, r.is_temporary, r.object_type, - r.object_identity, r.address_names, r.address_args; + r.object_identity, r.schema_name, r.object_name, + r.address_names, r.address_args; END LOOP; END; $$; CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop @@ -346,17 +359,37 @@ DECLARE r record; BEGIN FOR r IN SELECT * from pg_event_trigger_dropped_objects() LOOP - RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% name=% args=%', + RAISE NOTICE 'DROP: orig=% normal=% istemp=% type=% identity=% schema=% name=% addr=% args=%', r.original, r.normal, r.is_temporary, r.object_type, - r.object_identity, r.address_names, r.address_args; + r.object_identity, r.schema_name, r.object_name, + r.address_names, r.address_args; END LOOP; END; $$; +CREATE FUNCTION event_trigger_dummy_trigger() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RETURN new; +END; $$; + CREATE TABLE evtrg_nontemp_table (f1 int primary key, f2 int default 42); +CREATE TRIGGER evtrg_nontemp_trig + BEFORE INSERT ON evtrg_nontemp_table + EXECUTE FUNCTION event_trigger_dummy_trigger(); +CREATE POLICY evtrg_nontemp_pol ON evtrg_nontemp_table USING (f2 > 0); DROP TABLE evtrg_nontemp_table; + CREATE TEMP TABLE a_temp_tbl (f1 int primary key, f2 int default 42); +CREATE TRIGGER a_temp_trig + BEFORE INSERT ON a_temp_tbl + EXECUTE FUNCTION event_trigger_dummy_trigger(); +CREATE POLICY a_temp_pol ON a_temp_tbl USING (f2 > 0); DROP TABLE a_temp_tbl; +DROP FUNCTION event_trigger_dummy_trigger(); + -- CREATE OPERATOR CLASS without FAMILY clause should report -- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int; From 9a71989a8f61d7ee003c443a979a1bd43a08ff63 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 12 Sep 2025 18:10:11 -0400 Subject: [PATCH 56/96] Reject "ALTER DATABASE/USER ... RESET foo" with invalid GUC name. If the database or user had no entry in pg_db_role_setting, RESET silently did nothing --- including not checking the validity of the given GUC name. This is quite inconsistent and surprising, because you *would* get such an error if there were any pg_db_role_setting entry, even though it contains values for unrelated GUCs. While this is clearly a bug, changing it in stable branches seems unwise. The effect will be that some ALTER commands that formerly were no-ops will now be errors, and people don't like that sort of thing in minor releases. Author: Vitaly Davydov Reviewed-by: Tom Lane Discussion: https://postgr.es/m/30783e-68c28a00-9-41004480@130449754 --- src/backend/catalog/pg_db_role_setting.c | 9 +++++++++ .../modules/unsafe_tests/expected/setconfig.out | 13 +++++++++++++ src/test/modules/unsafe_tests/sql/setconfig.sql | 9 +++++++++ 3 files changed, 31 insertions(+) diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c index 090fc07c28acb..832e49a34bea5 100644 --- a/src/backend/catalog/pg_db_role_setting.c +++ b/src/backend/catalog/pg_db_role_setting.c @@ -151,6 +151,15 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) CatalogTupleInsert(rel, newtuple); } + else + { + /* + * RESET doesn't need to change any state if there's no pre-existing + * pg_db_role_setting entry, but for consistency we should still check + * that the option is valid and we're allowed to set it. + */ + (void) GUCArrayDelete(NULL, setstmt->name); + } InvokeObjectPostAlterHookArg(DbRoleSettingRelationId, databaseid, 0, roleid, false); diff --git a/src/test/modules/unsafe_tests/expected/setconfig.out b/src/test/modules/unsafe_tests/expected/setconfig.out index 5f42443e144b9..37e33709012c2 100644 --- a/src/test/modules/unsafe_tests/expected/setconfig.out +++ b/src/test/modules/unsafe_tests/expected/setconfig.out @@ -62,6 +62,19 @@ SELECT current_user, session_user; SET ROLE NONE; DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I RESET role', current_catalog); END$$; +-- Test some error cases +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I SET bogus = 0', current_catalog); END$$; +ERROR: unrecognized configuration parameter "bogus" +CONTEXT: SQL statement "ALTER DATABASE contrib_regression SET bogus = 0" +PL/pgSQL function inline_code_block line 1 at EXECUTE +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I RESET bogus', current_catalog); END$$; +ERROR: unrecognized configuration parameter "bogus" +CONTEXT: SQL statement "ALTER DATABASE contrib_regression RESET bogus" +PL/pgSQL function inline_code_block line 1 at EXECUTE +ALTER USER regress_authenticated_user_db_ssa RESET bogus; +ERROR: unrecognized configuration parameter "bogus" -- Test connection string options \c -reuse-previous=on "user=regress_authenticated_user_db_sr options=-crole=regress_current_user" SELECT current_user, session_user; diff --git a/src/test/modules/unsafe_tests/sql/setconfig.sql b/src/test/modules/unsafe_tests/sql/setconfig.sql index 81296d1091b47..d9e1fc908a125 100644 --- a/src/test/modules/unsafe_tests/sql/setconfig.sql +++ b/src/test/modules/unsafe_tests/sql/setconfig.sql @@ -50,6 +50,15 @@ DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I RESET role', current_catalog); END$$; +-- Test some error cases + +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I SET bogus = 0', current_catalog); END$$; +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I RESET bogus', current_catalog); END$$; +ALTER USER regress_authenticated_user_db_ssa RESET bogus; + + -- Test connection string options \c -reuse-previous=on "user=regress_authenticated_user_db_sr options=-crole=regress_current_user" From 88824e68611a88a4ef7218c093810a94f86e12e0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 12 Sep 2025 18:45:06 -0400 Subject: [PATCH 57/96] Avoid context dependency in test case added by 9a71989a8. It's not quite clear to me why this didn't show up in my local check-world testing, but some of the buildfarm evidently runs this test with a different database name. Adjust the test so that that doesn't affect the reported error messages. --- src/test/modules/unsafe_tests/expected/setconfig.out | 8 ++++---- src/test/modules/unsafe_tests/sql/setconfig.sql | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/modules/unsafe_tests/expected/setconfig.out b/src/test/modules/unsafe_tests/expected/setconfig.out index 37e33709012c2..5318f075d1e8c 100644 --- a/src/test/modules/unsafe_tests/expected/setconfig.out +++ b/src/test/modules/unsafe_tests/expected/setconfig.out @@ -63,18 +63,18 @@ SET ROLE NONE; DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I RESET role', current_catalog); END$$; -- Test some error cases +-- We have to use terse mode so that the database name doesn't +-- appear in the error output. +\set VERBOSITY terse DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I SET bogus = 0', current_catalog); END$$; ERROR: unrecognized configuration parameter "bogus" -CONTEXT: SQL statement "ALTER DATABASE contrib_regression SET bogus = 0" -PL/pgSQL function inline_code_block line 1 at EXECUTE DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I RESET bogus', current_catalog); END$$; ERROR: unrecognized configuration parameter "bogus" -CONTEXT: SQL statement "ALTER DATABASE contrib_regression RESET bogus" -PL/pgSQL function inline_code_block line 1 at EXECUTE ALTER USER regress_authenticated_user_db_ssa RESET bogus; ERROR: unrecognized configuration parameter "bogus" +\set VERBOSITY default -- Test connection string options \c -reuse-previous=on "user=regress_authenticated_user_db_sr options=-crole=regress_current_user" SELECT current_user, session_user; diff --git a/src/test/modules/unsafe_tests/sql/setconfig.sql b/src/test/modules/unsafe_tests/sql/setconfig.sql index d9e1fc908a125..4349490f94117 100644 --- a/src/test/modules/unsafe_tests/sql/setconfig.sql +++ b/src/test/modules/unsafe_tests/sql/setconfig.sql @@ -51,12 +51,16 @@ DO $$BEGIN EXECUTE format( -- Test some error cases +-- We have to use terse mode so that the database name doesn't +-- appear in the error output. +\set VERBOSITY terse DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I SET bogus = 0', current_catalog); END$$; DO $$BEGIN EXECUTE format( 'ALTER DATABASE %I RESET bogus', current_catalog); END$$; ALTER USER regress_authenticated_user_db_ssa RESET bogus; +\set VERBOSITY default -- Test connection string options From f6edf403a99923b98f8b8b3398c7ef32e1ae9a3e Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sat, 13 Sep 2025 20:38:52 +0300 Subject: [PATCH 58/96] Specify locale provider for pg_regress --no-locale pg_regress has a --no-locale option that forces the temporary database to have C locale. However, currently, locale C only exists in the 'builtin' locale provider. This makes 'pg_regress --no-locale' fail when the default locale provider is not 'builtin'. This commit makes 'pg_regress --no-locale' specify both LOCALE='C' and LOCALE_PROVIDER='builtin'. Discussion: https://postgr.es/m/b54921f95e23b4391b1613e9053a3d58%40postgrespro.ru Author: Oleg Tselebrovskiy Reviewed-by: Alexander Korotkov --- src/test/regress/pg_regress.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 5d85dcc62f0a5..61c035a39834a 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -1968,10 +1968,10 @@ create_database(const char *dbname) */ if (encoding) psql_add_command(buf, "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'%s", dbname, encoding, - (nolocale) ? " LOCALE='C'" : ""); + (nolocale) ? " LOCALE='C' LOCALE_PROVIDER='builtin'" : ""); else psql_add_command(buf, "CREATE DATABASE \"%s\" TEMPLATE=template0%s", dbname, - (nolocale) ? " LOCALE='C'" : ""); + (nolocale) ? " LOCALE='C' LOCALE_PROVIDER='builtin'" : ""); psql_add_command(buf, "ALTER DATABASE \"%s\" SET lc_messages TO 'C';" "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';" From 7e9c216b5236cc61f677787b35e8c8f28f5f6959 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Sat, 13 Sep 2025 14:50:02 -0500 Subject: [PATCH 59/96] Re-pgindent nbtpreprocesskeys.c after commit 796962922e. Backpatch-through: 18 --- src/backend/access/nbtree/nbtpreprocesskeys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/access/nbtree/nbtpreprocesskeys.c b/src/backend/access/nbtree/nbtpreprocesskeys.c index 07a3ff11a0b87..71ddd68fd548c 100644 --- a/src/backend/access/nbtree/nbtpreprocesskeys.c +++ b/src/backend/access/nbtree/nbtpreprocesskeys.c @@ -1498,7 +1498,7 @@ _bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk, */ lookupstrat = BTGreaterEqualStrategyNumber; if (low_compare->sk_flags & SK_BT_DESC) - lookupstrat = BTLessEqualStrategyNumber; /* commute this too */ + lookupstrat = BTLessEqualStrategyNumber; /* commute this too */ geop = get_opfamily_member(opfamily, opcintype, opcintype, lookupstrat); if (!OidIsValid(geop)) return; From 95bdc672282722fb52656a81fefe18296015708e Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Sat, 13 Sep 2025 14:55:38 -0500 Subject: [PATCH 60/96] Add commit 7e9c216b52 to .git-blame-ignore-revs. --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f83e2fc658664..65aecdffaca6e 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -14,6 +14,9 @@ # # $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso +7e9c216b5236cc61f677787b35e8c8f28f5f6959 # 2025-09-13 14:50:02 -0500 +# Re-pgindent nbtpreprocesskeys.c after commit 796962922e. + 1d1612aec7688139e1a5506df1366b4b6a69605d # 2025-07-29 09:10:41 -0400 # Run pgindent. From cdf7feb96562071f15ceb070272d7e84246d943d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 13 Sep 2025 16:55:51 -0400 Subject: [PATCH 61/96] Amend recent fix for SIMILAR TO regex conversion. Commit e3ffc3e91 fixed the translation of character classes in SIMILAR TO regular expressions. Unfortunately the fix broke a corner case: if there is an escape character right after the opening bracket (for example in "[\q]"), a closing bracket right after the escape sequence would not be seen as closing the character class. There were two more oversights: a backslash or a nested opening bracket right at the beginning of a character class should remove the special meaning from any following caret or closing bracket. This bug suggests that this code needs to be more readable, so also rename the variables "charclass_depth" and "charclass_start" to something more meaningful, rewrite an "if" cascade to be more consistent, and improve the commentary. Reported-by: Dominique Devienne Reported-by: Stephan Springl Author: Laurenz Albe Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAFCRh-8NwJd0jq6P=R3qhHyqU7hw0BTor3W0SvUcii24et+zAw@mail.gmail.com Backpatch-through: 13 --- src/backend/utils/adt/regexp.c | 97 +++++++++++++++++++-------- src/test/regress/expected/strings.out | 9 +++ src/test/regress/sql/strings.sql | 3 + 3 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c index 6e2864cbbda8c..b0cdef9b19fa2 100644 --- a/src/backend/utils/adt/regexp.c +++ b/src/backend/utils/adt/regexp.c @@ -774,10 +774,8 @@ similar_escape_internal(text *pat_text, text *esc_text) elen; bool afterescape = false; int nquotes = 0; - int charclass_depth = 0; /* Nesting level of character classes, - * encompassed by square brackets */ - int charclass_start = 0; /* State of the character class start, - * for carets */ + int bracket_depth = 0; /* square bracket nesting level */ + int charclass_pos = 0; /* position inside a character class */ p = VARDATA_ANY(pat_text); plen = VARSIZE_ANY_EXHDR(pat_text); @@ -836,6 +834,17 @@ similar_escape_internal(text *pat_text, text *esc_text) * the relevant part separators in the above expansion. If the result * of this function is used in a plain regexp match (SIMILAR TO), the * escape-double-quotes have no effect on the match behavior. + * + * While we don't fully validate character classes (bracket expressions), + * we do need to parse them well enough to know where they end. + * "charclass_pos" tracks where we are in a character class. + * Its value is uninteresting when bracket_depth is 0. + * But when bracket_depth > 0, it will be + * 1: right after the opening '[' (a following '^' will negate + * the class, while ']' is a literal character) + * 2: right after a '^' after the opening '[' (']' is still a literal + * character) + * 3 or more: further inside the character class (']' ends the class) *---------- */ @@ -907,7 +916,7 @@ similar_escape_internal(text *pat_text, text *esc_text) /* fast path */ if (afterescape) { - if (pchar == '"' && charclass_depth < 1) /* escape-double-quote? */ + if (pchar == '"' && bracket_depth < 1) /* escape-double-quote? */ { /* emit appropriate part separator, per notes above */ if (nquotes == 0) @@ -948,6 +957,12 @@ similar_escape_internal(text *pat_text, text *esc_text) */ *r++ = '\\'; *r++ = pchar; + + /* + * If we encounter an escaped character in a character class, + * we are no longer at the beginning. + */ + charclass_pos = 3; } afterescape = false; } @@ -956,41 +971,69 @@ similar_escape_internal(text *pat_text, text *esc_text) /* SQL escape character; do not send to output */ afterescape = true; } - else if (charclass_depth > 0) + else if (bracket_depth > 0) { + /* inside a character class */ if (pchar == '\\') + { + /* + * If we're here, backslash is not the SQL escape character, + * so treat it as a literal class element, which requires + * doubling it. (This matches our behavior for backslashes + * outside character classes.) + */ *r++ = '\\'; + } *r++ = pchar; - /* - * Ignore a closing bracket at the start of a character class. - * Such a bracket is taken literally rather than closing the - * class. "charclass_start" is 1 right at the beginning of a - * class and 2 after an initial caret. - */ - if (pchar == ']' && charclass_start > 2) - charclass_depth--; + /* parse the character class well enough to identify ending ']' */ + if (pchar == ']' && charclass_pos > 2) + { + /* found the real end of a bracket pair */ + bracket_depth--; + /* don't reset charclass_pos, this may be an inner bracket */ + } else if (pchar == '[') - charclass_depth++; + { + /* start of a nested bracket pair */ + bracket_depth++; - /* - * If there is a caret right after the opening bracket, it negates - * the character class, but a following closing bracket should - * still be treated as a normal character. That holds only for - * the first caret, so only the values 1 and 2 mean that closing - * brackets should be taken literally. - */ - if (pchar == '^') - charclass_start++; + /* + * We are no longer at the beginning of a character class. + * (The nested bracket pair is a collating element, not a + * character class in its own right.) + */ + charclass_pos = 3; + } + else if (pchar == '^') + { + /* + * A caret right after the opening bracket negates the + * character class. In that case, the following will + * increment charclass_pos from 1 to 2, so that a following + * ']' is still a literal character and does not end the + * character class. If we are further inside a character + * class, charclass_pos might get incremented past 3, which is + * fine. + */ + charclass_pos++; + } else - charclass_start = 3; /* definitely past the start */ + { + /* + * Anything else (including a backslash or leading ']') is an + * element of the character class, so we are no longer at the + * beginning of the class. + */ + charclass_pos = 3; + } } else if (pchar == '[') { /* start of a character class */ *r++ = pchar; - charclass_depth++; - charclass_start = 1; + bracket_depth = 1; + charclass_pos = 1; } else if (pchar == '%') { diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index ba302da51e7b2..2d6cb02ad6085 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -693,6 +693,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[^^]^'; Filter: (f1 ~ '^(?:[^^]\^)$'::text) (2 rows) +-- Closing square bracket after an escape sequence at the beginning of +-- a character closes the character class +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[|a]%' ESCAPE '|'; + QUERY PLAN +--------------------------------------- + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:[\a].*)$'::text) +(2 rows) + -- Test backslash escapes in regexp_replace's replacement string SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); regexp_replace diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index b94004cc08ce6..5ed421d62059d 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -218,6 +218,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[]%][^]%][^%]%'; -- Closing square bracket effective after two carets at the beginning -- of character class. EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[^^]^'; +-- Closing square bracket after an escape sequence at the beginning of +-- a character closes the character class +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[|a]%' ESCAPE '|'; -- Test backslash escapes in regexp_replace's replacement string SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); From 454c046094ab3431c2ce0c540c46e623bc05bd1a Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Sat, 13 Sep 2025 21:01:33 -0400 Subject: [PATCH 62/96] nbtree: Always set skipScan flag on rescan. The TimescaleDB extension expects to be able to change an nbtree scan's keys across rescans. The issue arises in the extension's implementation of loose index scan. This is arguably a misuse of the index AM API, though apparently it worked until recently. It stopped working when the skipScan flag was added to BTScanOpaqueData by commit 8a510275, though. The flag wouldn't reliably track whether the scan (actually, the current rescan) has any skip arrays, leading to confusion in _bt_set_startikey. nbtree preprocessing will now defensively initialize the scan's skipScan flag in all cases, including the case where _bt_preprocess_array_keys returns early due to the (re)scan not using arrays. While nbtree isn't obligated to support this use case (at least not according to my reading of the index AM API), it still seems like a good idea to be consistent here, on general robustness grounds. Author: Peter Geoghegan Reported-By: Natalya Aksman Discussion: https://postgr.es/m/CAJumhcirfMojbk20+W0YimbNDkwdECvJprQGQ-XqK--ph09nQw@mail.gmail.com Backpatch-through: 18 --- src/backend/access/nbtree/nbtpreprocesskeys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/access/nbtree/nbtpreprocesskeys.c b/src/backend/access/nbtree/nbtpreprocesskeys.c index 71ddd68fd548c..7b7d7860d8f53 100644 --- a/src/backend/access/nbtree/nbtpreprocesskeys.c +++ b/src/backend/access/nbtree/nbtpreprocesskeys.c @@ -1854,6 +1854,7 @@ _bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys) * (also checks if we should add extra skip arrays based on input keys) */ numArrayKeys = _bt_num_array_keys(scan, skip_eq_ops, &numSkipArrayKeys); + so->skipScan = (numSkipArrayKeys > 0); /* Quit if nothing to do. */ if (numArrayKeys == 0) @@ -1883,7 +1884,6 @@ _bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys) arrayKeyData = (ScanKey) palloc(numArrayKeyData * sizeof(ScanKeyData)); /* Allocate space for per-array data in the workspace context */ - so->skipScan = (numSkipArrayKeys > 0); so->arrayKeys = (BTArrayKeyInfo *) palloc(numArrayKeys * sizeof(BTArrayKeyInfo)); /* Allocate space for ORDER procs used to help _bt_checkkeys */ From bf5da5d6cae959d8119b4b137ddd9912292b8014 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 07:25:22 +0200 Subject: [PATCH 63/96] Hide duplicate names from extension views If extensions of equal names were installed in different directories in the path, the views pg_available_extensions and pg_available_extension_versions would show all of them, even though only the first one was actually reachable by CREATE EXTENSION. To fix, have those views skip extensions found later in the path if they have names already found earlier. Also add a bit of documentation that only the first extension in the path can be used. Reported-by: Pierrick Discussion: https://www.postgresql.org/message-id/flat/8f5a0517-1cb8-4085-ae89-77e7454e27ba%40dalibo.com --- doc/src/sgml/config.sgml | 6 +++++ src/backend/commands/extension.c | 24 +++++++++++++++++++ .../t/001_extension_control_path.pl | 9 ++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 3c33d5d0fbcae..e9b420f3ddbef 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11065,6 +11065,12 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system' string, the default '$system' is also assumed. + + If extensions with equal names are present in multiple directories in + the configured path, only the instance found first in the path will be + used. + + This parameter can be changed at run time by superusers and users with the appropriate SET privilege, but a diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index e6f9ab6dfd66b..93ef1ad106fdb 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -2208,6 +2208,7 @@ pg_available_extensions(PG_FUNCTION_ARGS) List *locations; DIR *dir; struct dirent *de; + List *found_ext = NIL; /* Build tuplestore to hold the result rows */ InitMaterializedSRF(fcinfo, 0); @@ -2232,6 +2233,7 @@ pg_available_extensions(PG_FUNCTION_ARGS) { ExtensionControlFile *control; char *extname; + String *extname_str; Datum values[3]; bool nulls[3]; @@ -2246,6 +2248,16 @@ pg_available_extensions(PG_FUNCTION_ARGS) if (strstr(extname, "--")) continue; + /* + * Ignore already-found names. They are not reachable by the + * path search, so don't shown them. + */ + extname_str = makeString(extname); + if (list_member(found_ext, extname_str)) + continue; + else + found_ext = lappend(found_ext, extname_str); + control = new_ExtensionControlFile(extname); control->control_dir = pstrdup(location); parse_extension_control_file(control, NULL); @@ -2294,6 +2306,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) List *locations; DIR *dir; struct dirent *de; + List *found_ext = NIL; /* Build tuplestore to hold the result rows */ InitMaterializedSRF(fcinfo, 0); @@ -2318,6 +2331,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) { ExtensionControlFile *control; char *extname; + String *extname_str; if (!is_extension_control_filename(de->d_name)) continue; @@ -2330,6 +2344,16 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) if (strstr(extname, "--")) continue; + /* + * Ignore already-found names. They are not reachable by the + * path search, so don't shown them. + */ + extname_str = makeString(extname); + if (list_member(found_ext, extname_str)) + continue; + else + found_ext = lappend(found_ext, extname_str); + /* read the control file */ control = new_ExtensionControlFile(extname); control->control_dir = pstrdup(location); diff --git a/src/test/modules/test_extensions/t/001_extension_control_path.pl b/src/test/modules/test_extensions/t/001_extension_control_path.pl index 1a9c97bbf4dcc..7fbe5bde33227 100644 --- a/src/test/modules/test_extensions/t/001_extension_control_path.pl +++ b/src/test/modules/test_extensions/t/001_extension_control_path.pl @@ -11,12 +11,15 @@ $node->init; -# Create a temporary directory for the extension control file +# Create temporary directories for the extension control files my $ext_dir = PostgreSQL::Test::Utils::tempdir(); mkpath("$ext_dir/extension"); +my $ext_dir2 = PostgreSQL::Test::Utils::tempdir(); +mkpath("$ext_dir2/extension"); my $ext_name = "test_custom_ext_paths"; create_extension($ext_name, $ext_dir); +create_extension($ext_name, $ext_dir2); my $ext_name2 = "test_custom_ext_paths_using_directory"; mkpath("$ext_dir/$ext_name2"); @@ -26,7 +29,7 @@ my $sep = $windows_os ? ";" : ":"; $node->append_conf( 'postgresql.conf', qq{ -extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir ]}' +extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir ]}$sep@{[ $windows_os ? ($ext_dir2 =~ s/\\/\\\\/gr) : $ext_dir2 ]}' }); # Start node @@ -34,7 +37,7 @@ my $ecp = $node->safe_psql('postgres', 'show extension_control_path;'); -is($ecp, "\$system$sep$ext_dir", +is($ecp, "\$system$sep$ext_dir$sep$ext_dir2", "custom extension control directory path configured"); $node->safe_psql('postgres', "CREATE EXTENSION $ext_name"); From 748caa9dcb686a84f239ee2cc08030d7c1efddd4 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 07:27:31 +0200 Subject: [PATCH 64/96] Some stylistic improvements in toast_save_datum() Move some variables to a smaller scope. Initialize chunk_data before storing a pointer to it; this avoids compiler warnings on clang-21, or respectively us having to work around it by initializing it to zero before the variable is used (as was done in commit e92677e8633). Discussion: https://www.postgresql.org/message-id/flat/6604ad6e-5934-43ac-8590-15113d6ae4b1%40eisentraut.org --- src/backend/access/common/toast_internals.c | 39 +++++++++------------ 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 75e908c2e80a7..81dbd67c72587 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -121,22 +121,10 @@ toast_save_datum(Relation rel, Datum value, { Relation toastrel; Relation *toastidxs; - HeapTuple toasttup; TupleDesc toasttupDesc; - Datum t_values[3]; - bool t_isnull[3]; CommandId mycid = GetCurrentCommandId(true); struct varlena *result; struct varatt_external toast_pointer; - union - { - struct varlena hdr; - /* this is to make the union big enough for a chunk: */ - char data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ]; - /* ensure union is aligned well enough: */ - int32 align_it; - } chunk_data = {0}; /* silence compiler warning */ - int32 chunk_size; int32 chunk_seq = 0; char *data_p; int32 data_todo; @@ -289,21 +277,23 @@ toast_save_datum(Relation rel, Datum value, } } - /* - * Initialize constant parts of the tuple data - */ - t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid); - t_values[2] = PointerGetDatum(&chunk_data); - t_isnull[0] = false; - t_isnull[1] = false; - t_isnull[2] = false; - /* * Split up the item into chunks */ while (data_todo > 0) { - int i; + HeapTuple toasttup; + Datum t_values[3]; + bool t_isnull[3] = {0}; + union + { + struct varlena hdr; + /* this is to make the union big enough for a chunk: */ + char data[TOAST_MAX_CHUNK_SIZE + VARHDRSZ]; + /* ensure union is aligned well enough: */ + int32 align_it; + } chunk_data; + int32 chunk_size; CHECK_FOR_INTERRUPTS(); @@ -315,9 +305,12 @@ toast_save_datum(Relation rel, Datum value, /* * Build a tuple and store it */ + t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid); t_values[1] = Int32GetDatum(chunk_seq++); SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ); memcpy(VARDATA(&chunk_data), data_p, chunk_size); + t_values[2] = PointerGetDatum(&chunk_data); + toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull); heap_insert(toastrel, toasttup, mycid, options, NULL); @@ -333,7 +326,7 @@ toast_save_datum(Relation rel, Datum value, * Note also that there had better not be any user-created index on * the TOAST table, since we don't bother to update anything else. */ - for (i = 0; i < num_indexes; i++) + for (int i = 0; i < num_indexes; i++) { /* Only index relations marked as ready can be updated */ if (toastidxs[i]->rd_index->indisready) From 282d0bdee6192f1a859ee34672ae73abf49794dc Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 08:13:21 +0200 Subject: [PATCH 65/96] jit: fix build with LLVM-21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLVM-21 renamed llvm::GlobalValue::getGUID() to getGUIDAssumingExternalLinkage(), so add a version guard. Author: Holger Hoffstätte Discussion: https://www.postgresql.org/message-id/flat/d25e6e4a-d1b4-84d3-2f8a-6c45b975f53d%40applied-asynchrony.com --- src/backend/jit/llvm/llvmjit_inline.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/jit/llvm/llvmjit_inline.cpp b/src/backend/jit/llvm/llvmjit_inline.cpp index 2764c3bbe2f03..51b32cd9f940d 100644 --- a/src/backend/jit/llvm/llvmjit_inline.cpp +++ b/src/backend/jit/llvm/llvmjit_inline.cpp @@ -238,7 +238,11 @@ llvm_build_inline_plan(LLVMContextRef lc, llvm::Module *mod) llvm_split_symbol_name(symbolName.data(), &cmodname, &cfuncname); +#if LLVM_VERSION_MAJOR >= 21 + funcGUID = llvm::GlobalValue::getGUIDAssumingExternalLinkage(cfuncname); +#else funcGUID = llvm::GlobalValue::getGUID(cfuncname); +#endif /* already processed */ if (inlineState.processed) From 0d48d393d465b6f1abe18b86bd5ac2de0636a40e Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 15 Sep 2025 08:44:54 +0000 Subject: [PATCH 66/96] Resume conflict-relevant data retention automatically. This commit resumes automatic retention of conflict-relevant data for a subscription. Previously, retention would stop if the apply process failed to advance its xmin (oldest_nonremovable_xid) within the configured max_retention_duration and user needs to manually re-enable retain_dead_tuples option. With this change, retention will resume automatically once the apply worker catches up and begins advancing its xmin (oldest_nonremovable_xid) within the configured threshold. Author: Zhijie Hou Reviewed-by: shveta malik Reviewed-by: Amit Kapila Reviewed-by: Dilip Kumar Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/OS0PR01MB5716BE80DAEB0EE2A6A5D1F5949D2@OS0PR01MB5716.jpnprd01.prod.outlook.com --- doc/src/sgml/ref/create_subscription.sgml | 9 +- src/backend/replication/logical/launcher.c | 10 +- src/backend/replication/logical/worker.c | 200 ++++++++++++++++----- src/test/subscription/t/035_conflicts.pl | 27 +++ 4 files changed, 196 insertions(+), 50 deletions(-) diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml index fc3144373110f..ed82cf1809e55 100644 --- a/doc/src/sgml/ref/create_subscription.sgml +++ b/doc/src/sgml/ref/create_subscription.sgml @@ -538,10 +538,11 @@ CREATE SUBSCRIPTION subscription_nameretain_dead_tuples is enabled, confirm that the retention duration has exceeded the max_retention_duration set within the corresponding - subscription. The retention will not be automatically resumed unless a - new subscription is created with retain_dead_tuples = - true, or the user manually re-enables - retain_dead_tuples. + subscription. The retention will automatically resume when at least one + apply worker confirms that the retention duration is within the + specified limit, or when a new subscription is created with + retain_dead_tuples = true. Alternatively, retention + can be manually resumed by re-enabling retain_dead_tuples. Note that overall retention will not stop if other subscriptions that diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index add2e2e066c38..c900b6cf3b163 100644 --- a/src/backend/replication/logical/launcher.c +++ b/src/backend/replication/logical/launcher.c @@ -1261,24 +1261,30 @@ ApplyLauncherMain(Datum main_arg) LWLockAcquire(LogicalRepWorkerLock, LW_SHARED); w = logicalrep_worker_find(sub->oid, InvalidOid, false); - LWLockRelease(LogicalRepWorkerLock); if (w != NULL) { /* * Compute the minimum xmin required to protect dead tuples * required for conflict detection among all running apply - * workers. + * workers. This computation is performed while holding + * LogicalRepWorkerLock to prevent accessing invalid worker + * data, in scenarios where a worker might exit and reset its + * state concurrently. */ if (sub->retaindeadtuples && sub->retentionactive && can_update_xmin) compute_min_nonremovable_xid(w, &xmin); + LWLockRelease(LogicalRepWorkerLock); + /* worker is running already */ continue; } + LWLockRelease(LogicalRepWorkerLock); + /* * Can't advance xmin of the slot unless all the workers * corresponding to subscriptions actively retaining dead tuples diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index ee6ac22329fdc..9b5885d57cf80 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -181,6 +181,15 @@ * pg_subscription.subretentionactive is updated to false within a new * transaction, and oldest_nonremovable_xid is set to InvalidTransactionId. * + * - RDT_RESUME_CONFLICT_INFO_RETENTION: + * This phase is required only when max_retention_duration is defined. We + * enter this phase if the retention was previously stopped, and the time + * required to advance the non-removable transaction ID in the + * RDT_WAIT_FOR_LOCAL_FLUSH phase has decreased to within acceptable limits + * (or if max_retention_duration is set to 0). During this phase, + * pg_subscription.subretentionactive is updated to true within a new + * transaction, and the worker will be restarted. + * * The overall state progression is: GET_CANDIDATE_XID -> * REQUEST_PUBLISHER_STATUS -> WAIT_FOR_PUBLISHER_STATUS -> (loop to * REQUEST_PUBLISHER_STATUS till concurrent remote transactions end) -> @@ -381,7 +390,8 @@ typedef enum RDT_REQUEST_PUBLISHER_STATUS, RDT_WAIT_FOR_PUBLISHER_STATUS, RDT_WAIT_FOR_LOCAL_FLUSH, - RDT_STOP_CONFLICT_INFO_RETENTION + RDT_STOP_CONFLICT_INFO_RETENTION, + RDT_RESUME_CONFLICT_INFO_RETENTION, } RetainDeadTuplesPhase; /* @@ -568,10 +578,14 @@ static void wait_for_publisher_status(RetainDeadTuplesData *rdt_data, static void wait_for_local_flush(RetainDeadTuplesData *rdt_data); static bool should_stop_conflict_info_retention(RetainDeadTuplesData *rdt_data); static void stop_conflict_info_retention(RetainDeadTuplesData *rdt_data); +static void resume_conflict_info_retention(RetainDeadTuplesData *rdt_data); +static bool update_retention_status(bool active); static void reset_retention_data_fields(RetainDeadTuplesData *rdt_data); static void adjust_xid_advance_interval(RetainDeadTuplesData *rdt_data, bool new_xid_found); +static void apply_worker_exit(void); + static void apply_handle_commit_internal(LogicalRepCommitData *commit_data); static void apply_handle_insert_internal(ApplyExecutionData *edata, ResultRelInfo *relinfo, @@ -4367,10 +4381,6 @@ can_advance_nonremovable_xid(RetainDeadTuplesData *rdt_data) if (!MySubscription->retaindeadtuples) return false; - /* No need to advance if we have already stopped retaining */ - if (!MySubscription->retentionactive) - return false; - return true; } @@ -4399,6 +4409,9 @@ process_rdt_phase_transition(RetainDeadTuplesData *rdt_data, case RDT_STOP_CONFLICT_INFO_RETENTION: stop_conflict_info_retention(rdt_data); break; + case RDT_RESUME_CONFLICT_INFO_RETENTION: + resume_conflict_info_retention(rdt_data); + break; } } @@ -4522,7 +4535,10 @@ wait_for_publisher_status(RetainDeadTuplesData *rdt_data, * retaining conflict information for this worker. */ if (should_stop_conflict_info_retention(rdt_data)) + { + rdt_data->phase = RDT_STOP_CONFLICT_INFO_RETENTION; return; + } if (!FullTransactionIdIsValid(rdt_data->remote_wait_for)) rdt_data->remote_wait_for = rdt_data->remote_nextxid; @@ -4643,7 +4659,10 @@ wait_for_local_flush(RetainDeadTuplesData *rdt_data) * retaining conflict information for this worker. */ if (should_stop_conflict_info_retention(rdt_data)) + { + rdt_data->phase = RDT_STOP_CONFLICT_INFO_RETENTION; return; + } /* * Update and check the remote flush position if we are applying changes @@ -4672,6 +4691,21 @@ wait_for_local_flush(RetainDeadTuplesData *rdt_data) if (last_flushpos < rdt_data->remote_lsn) return; + /* + * Reaching this point implies should_stop_conflict_info_retention() + * returned false earlier, meaning that the most recent duration for + * advancing the non-removable transaction ID is within the + * max_retention_duration or max_retention_duration is set to 0. + * + * Therefore, if conflict info retention was previously stopped due to a + * timeout, it is now safe to resume retention. + */ + if (!MySubscription->retentionactive) + { + rdt_data->phase = RDT_RESUME_CONFLICT_INFO_RETENTION; + return; + } + /* * Reaching here means the remote WAL position has been received, and all * transactions up to that position on the publisher have been applied and @@ -4698,13 +4732,7 @@ wait_for_local_flush(RetainDeadTuplesData *rdt_data) * Check whether conflict information retention should be stopped due to * exceeding the maximum wait time (max_retention_duration). * - * If retention should be stopped, transition to the - * RDT_STOP_CONFLICT_INFO_RETENTION phase and return true. Otherwise, return - * false. - * - * Note: Retention won't be resumed automatically. The user must manually - * disable retain_dead_tuples and re-enable it after confirming that the - * replication slot maintained by the launcher has been dropped. + * If retention should be stopped, return true. Otherwise, return false. */ static bool should_stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) @@ -4735,11 +4763,6 @@ should_stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) rdt_data->table_sync_wait_time)) return false; - rdt_data->phase = RDT_STOP_CONFLICT_INFO_RETENTION; - - /* process the next phase */ - process_rdt_phase_transition(rdt_data, false); - return true; } @@ -4748,6 +4771,86 @@ should_stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) */ static void stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) +{ + /* Stop retention if not yet */ + if (MySubscription->retentionactive) + { + /* + * If the retention status cannot be updated (e.g., due to active + * transaction), skip further processing to avoid inconsistent + * retention behavior. + */ + if (!update_retention_status(false)) + return; + + SpinLockAcquire(&MyLogicalRepWorker->relmutex); + MyLogicalRepWorker->oldest_nonremovable_xid = InvalidTransactionId; + SpinLockRelease(&MyLogicalRepWorker->relmutex); + + ereport(LOG, + errmsg("logical replication worker for subscription \"%s\" has stopped retaining the information for detecting conflicts", + MySubscription->name), + errdetail("Retention is stopped as the apply process is not advancing its xmin within the configured max_retention_duration of %u ms.", + MySubscription->maxretention)); + } + + Assert(!TransactionIdIsValid(MyLogicalRepWorker->oldest_nonremovable_xid)); + + /* + * If retention has been stopped, reset to the initial phase to retry + * resuming retention. This reset is required to recalculate the current + * wait time and resume retention if the time falls within + * max_retention_duration. + */ + reset_retention_data_fields(rdt_data); +} + +/* + * Workhorse for the RDT_RESUME_CONFLICT_INFO_RETENTION phase. + */ +static void +resume_conflict_info_retention(RetainDeadTuplesData *rdt_data) +{ + /* We can't resume retention without updating retention status. */ + if (!update_retention_status(true)) + return; + + ereport(LOG, + errmsg("logical replication worker for subscription \"%s\" will resume retaining the information for detecting conflicts", + MySubscription->name), + MySubscription->maxretention + ? errdetail("Retention is re-enabled as the apply process is advancing its xmin within the configured max_retention_duration of %u ms.", + MySubscription->maxretention) + : errdetail("Retention is re-enabled as max_retention_duration is set to unlimited.")); + + /* + * Restart the worker to let the launcher initialize + * oldest_nonremovable_xid at startup. + * + * While it's technically possible to derive this value on-the-fly using + * the conflict detection slot's xmin, doing so risks a race condition: + * the launcher might clean slot.xmin just after retention resumes. This + * would make oldest_nonremovable_xid unreliable, especially during xid + * wraparound. + * + * Although this can be prevented by introducing heavy weight locking, the + * complexity it will bring doesn't seem worthwhile given how rarely + * retention is resumed. + */ + apply_worker_exit(); +} + +/* + * Updates pg_subscription.subretentionactive to the given value within a + * new transaction. + * + * If already inside an active transaction, skips the update and returns + * false. + * + * Returns true if the update is successfully performed. + */ +static bool +update_retention_status(bool active) { /* * Do not update the catalog during an active transaction. The transaction @@ -4755,7 +4858,7 @@ stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) * rollback of catalog updates if the application fails subsequently. */ if (IsTransactionState()) - return; + return false; StartTransactionCommand(); @@ -4765,26 +4868,18 @@ stop_conflict_info_retention(RetainDeadTuplesData *rdt_data) */ PushActiveSnapshot(GetTransactionSnapshot()); - /* Set pg_subscription.subretentionactive to false */ - UpdateDeadTupleRetentionStatus(MySubscription->oid, false); + /* Update pg_subscription.subretentionactive */ + UpdateDeadTupleRetentionStatus(MySubscription->oid, active); PopActiveSnapshot(); CommitTransactionCommand(); - SpinLockAcquire(&MyLogicalRepWorker->relmutex); - MyLogicalRepWorker->oldest_nonremovable_xid = InvalidTransactionId; - SpinLockRelease(&MyLogicalRepWorker->relmutex); - - ereport(LOG, - errmsg("logical replication worker for subscription \"%s\" has stopped retaining the information for detecting conflicts", - MySubscription->name), - errdetail("Retention of information used for conflict detection has exceeded max_retention_duration of %u ms.", - MySubscription->maxretention)); - /* Notify launcher to update the conflict slot */ ApplyLauncherWakeup(); - reset_retention_data_fields(rdt_data); + MySubscription->retentionactive = active; + + return true; } /* @@ -4809,19 +4904,20 @@ reset_retention_data_fields(RetainDeadTuplesData *rdt_data) /* * Adjust the interval for advancing non-removable transaction IDs. * - * If there is no activity on the node, we progressively double the interval - * used to advance non-removable transaction ID. This helps conserve CPU - * and network resources when there's little benefit to frequent updates. + * If there is no activity on the node or retention has been stopped, we + * progressively double the interval used to advance non-removable transaction + * ID. This helps conserve CPU and network resources when there's little benefit + * to frequent updates. * * The interval is capped by the lowest of the following: - * - wal_receiver_status_interval (if set), + * - wal_receiver_status_interval (if set and retention is active), * - a default maximum of 3 minutes, - * - max_retention_duration. + * - max_retention_duration (if retention is active). * - * This ensures the interval never exceeds the retention boundary, even if - * other limits are higher. Once activity resumes on the node, the interval - * is reset to lesser of 100ms and max_retention_duration, allowing timely - * advancement of non-removable transaction ID. + * This ensures the interval never exceeds the retention boundary, even if other + * limits are higher. Once activity resumes on the node and the retention is + * active, the interval is reset to lesser of 100ms and max_retention_duration, + * allowing timely advancement of non-removable transaction ID. * * XXX The use of wal_receiver_status_interval is a bit arbitrary so we can * consider the other interval or a separate GUC if the need arises. @@ -4829,7 +4925,7 @@ reset_retention_data_fields(RetainDeadTuplesData *rdt_data) static void adjust_xid_advance_interval(RetainDeadTuplesData *rdt_data, bool new_xid_found) { - if (!new_xid_found && rdt_data->xid_advance_interval) + if (rdt_data->xid_advance_interval && !new_xid_found) { int max_interval = wal_receiver_status_interval ? wal_receiver_status_interval * 1000 @@ -4842,6 +4938,18 @@ adjust_xid_advance_interval(RetainDeadTuplesData *rdt_data, bool new_xid_found) rdt_data->xid_advance_interval = Min(rdt_data->xid_advance_interval * 2, max_interval); } + else if (rdt_data->xid_advance_interval && + !MySubscription->retentionactive) + { + /* + * Retention has been stopped, so double the interval-capped at a + * maximum of 3 minutes. The wal_receiver_status_interval is + * intentionally not used as a upper bound, since the likelihood of + * retention resuming is lower than that of general activity resuming. + */ + rdt_data->xid_advance_interval = Min(rdt_data->xid_advance_interval * 2, + MAX_XID_ADVANCE_INTERVAL); + } else { /* @@ -4851,9 +4959,13 @@ adjust_xid_advance_interval(RetainDeadTuplesData *rdt_data, bool new_xid_found) rdt_data->xid_advance_interval = MIN_XID_ADVANCE_INTERVAL; } - /* Ensure the wait time remains within the maximum limit */ - rdt_data->xid_advance_interval = Min(rdt_data->xid_advance_interval, - MySubscription->maxretention); + /* + * Ensure the wait time remains within the maximum retention time limit + * when retention is active. + */ + if (MySubscription->retentionactive) + rdt_data->xid_advance_interval = Min(rdt_data->xid_advance_interval, + MySubscription->maxretention); } /* diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index 880551fc69d74..f2aee0f70df21 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -631,6 +631,33 @@ $node_B->adjust_conf('postgresql.conf', 'synchronized_standby_slots', "''"); $node_B->reload; +############################################################################### +# Check that dead tuple retention resumes when the max_retention_duration is set +# 0. +############################################################################### + +$log_offset = -s $node_A->logfile; + +# Set max_retention_duration to 0 +$node_A->safe_psql('postgres', + "ALTER SUBSCRIPTION $subname_AB SET (max_retention_duration = 0);"); + +# Confirm that the retention resumes +$node_A->wait_for_log( + qr/logical replication worker for subscription "tap_sub_a_b" will resume retaining the information for detecting conflicts +.*DETAIL:.* Retention is re-enabled as max_retention_duration is set to unlimited.*/, + $log_offset); + +ok( $node_A->poll_query_until( + 'postgres', + "SELECT xmin IS NOT NULL from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'" + ), + "the xmin value of slot 'pg_conflict_detection' is valid on Node A"); + +$result = $node_A->safe_psql('postgres', + "SELECT subretentionactive FROM pg_subscription WHERE subname='$subname_AB';"); +is($result, qq(t), 'retention is active'); + ############################################################################### # Check that the replication slot pg_conflict_detection is dropped after # removing all the subscriptions. From 1e3b5edb8eb236f16529ca8dae917996ad1f433e Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 10:53:14 +0200 Subject: [PATCH 67/96] Remove workarounds against repeat typedefs This is allowed in C11, so we don't need the workarounds anymore. Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- src/include/nodes/pathnodes.h | 23 ++++------------------- src/include/optimizer/optimizer.h | 13 +------------ 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 4a903d1ec1832..b12a2508d8c85 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -198,9 +198,6 @@ typedef struct PlannerGlobal * original Query. Note that at present the planner extensively modifies * the passed-in Query data structure; someday that should stop. * - * For reasons explained in optimizer/optimizer.h, we define the typedef - * either here or in that header, whichever is read first. - * * Not all fields are printed. (In some cases, there is no print support for * the field type; in others, doing so would lead to infinite recursion or * bloat dump output more than seems useful.) @@ -211,10 +208,7 @@ typedef struct PlannerGlobal * correctly replaced with the keeping one. *---------- */ -#ifndef HAVE_PLANNERINFO_TYPEDEF typedef struct PlannerInfo PlannerInfo; -#define HAVE_PLANNERINFO_TYPEDEF 1 -#endif struct PlannerInfo { @@ -1161,14 +1155,10 @@ typedef struct RelOptInfo * (by plancat.c), indrestrictinfo and predOK are set later, in * check_index_predicates(). */ -#ifndef HAVE_INDEXOPTINFO_TYPEDEF -typedef struct IndexOptInfo IndexOptInfo; -#define HAVE_INDEXOPTINFO_TYPEDEF 1 -#endif struct IndexPath; /* forward declaration */ -struct IndexOptInfo +typedef struct IndexOptInfo { pg_node_attr(no_copy_equal, no_read, no_query_jumble) @@ -1270,7 +1260,7 @@ struct IndexOptInfo /* AM's cost estimator */ /* Rather than include amapi.h here, we declare amcostestimate like this */ void (*amcostestimate) (struct PlannerInfo *, struct IndexPath *, double, Cost *, Cost *, Selectivity *, double *, double *) pg_node_attr(read_write_ignore); -}; +} IndexOptInfo; /* * ForeignKeyOptInfo @@ -3031,12 +3021,7 @@ typedef struct PlaceHolderVar * We also create transient SpecialJoinInfos for child joins during * partitionwise join planning, which are also not present in join_info_list. */ -#ifndef HAVE_SPECIALJOININFO_TYPEDEF -typedef struct SpecialJoinInfo SpecialJoinInfo; -#define HAVE_SPECIALJOININFO_TYPEDEF 1 -#endif - -struct SpecialJoinInfo +typedef struct SpecialJoinInfo { pg_node_attr(no_read, no_query_jumble) @@ -3057,7 +3042,7 @@ struct SpecialJoinInfo bool semi_can_hash; /* true if semi_operators are all hash */ List *semi_operators; /* OIDs of equality join operators */ List *semi_rhs_exprs; /* righthand-side expressions of these ops */ -}; +} SpecialJoinInfo; /* * Transient outer-join clause info. diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index 37bc13c2cbde6..a42abac01bf0b 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -28,22 +28,11 @@ * We don't want to include nodes/pathnodes.h here, because non-planner * code should generally treat PlannerInfo as an opaque typedef. * But we'd like such code to use that typedef name, so define the - * typedef either here or in pathnodes.h, whichever is read first. + * typedef both here and in pathnodes.h. */ -#ifndef HAVE_PLANNERINFO_TYPEDEF typedef struct PlannerInfo PlannerInfo; -#define HAVE_PLANNERINFO_TYPEDEF 1 -#endif - -/* Likewise for IndexOptInfo and SpecialJoinInfo. */ -#ifndef HAVE_INDEXOPTINFO_TYPEDEF typedef struct IndexOptInfo IndexOptInfo; -#define HAVE_INDEXOPTINFO_TYPEDEF 1 -#endif -#ifndef HAVE_SPECIALJOININFO_TYPEDEF typedef struct SpecialJoinInfo SpecialJoinInfo; -#define HAVE_SPECIALJOININFO_TYPEDEF 1 -#endif /* It also seems best not to include plannodes.h, params.h, or htup.h here */ struct PlannedStmt; From 70407d39b7ea8fd41496489b5f6a30c285e7d7d0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 10:48:30 +0200 Subject: [PATCH 68/96] Improve ExplainState type handling in header files Now that we can have repeat typedefs with C11, we don't need to use "struct ExplainState" anymore but can instead make a typedef where necessary. This doesn't change anything but makes it look nicer. (There are more opportunities for similar changes, but this is broken out because there was a separate discussion about it, and it's somewhat bulky on its own.) Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/f36c0a45-98cd-40b2-a7cc-f2bf02b12890%40eisentraut.org#a12fb1a2c1089d6d03010f6268871b00 Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- doc/src/sgml/fdwhandler.sgml | 2 +- src/include/commands/explain.h | 24 ++++++++--------- src/include/commands/explain_dr.h | 5 ++-- src/include/commands/explain_format.h | 37 ++++++++++++++------------- src/include/commands/explain_state.h | 2 +- src/include/foreign/fdwapi.h | 10 ++++---- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index b80320504d695..c6d66414b8ea7 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -1320,7 +1320,7 @@ ExplainForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, - struct ExplainState *es); + ExplainState *es); Print additional EXPLAIN output for a foreign table update. diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 3b122f79ed848..6e51d50efc73d 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -16,13 +16,13 @@ #include "executor/executor.h" #include "parser/parse_node.h" -struct ExplainState; /* defined in explain_state.h */ +typedef struct ExplainState ExplainState; /* defined in explain_state.h */ /* Hook for plugins to get control in ExplainOneQuery() */ typedef void (*ExplainOneQuery_hook_type) (Query *query, int cursorOptions, IntoClause *into, - struct ExplainState *es, + ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); @@ -31,7 +31,7 @@ extern PGDLLIMPORT ExplainOneQuery_hook_type ExplainOneQuery_hook; /* Hook for EXPLAIN plugins to print extra information for each plan */ typedef void (*explain_per_plan_hook_type) (PlannedStmt *plannedstmt, IntoClause *into, - struct ExplainState *es, + ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); @@ -42,7 +42,7 @@ typedef void (*explain_per_node_hook_type) (PlanState *planstate, List *ancestors, const char *relationship, const char *plan_name, - struct ExplainState *es); + ExplainState *es); extern PGDLLIMPORT explain_per_node_hook_type explain_per_node_hook; /* Hook for plugins to get control in explain_get_index_name() */ @@ -53,32 +53,32 @@ extern PGDLLIMPORT explain_get_index_name_hook_type explain_get_index_name_hook; extern void ExplainQuery(ParseState *pstate, ExplainStmt *stmt, ParamListInfo params, DestReceiver *dest); extern void standard_ExplainOneQuery(Query *query, int cursorOptions, - IntoClause *into, struct ExplainState *es, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); extern TupleDesc ExplainResultDesc(ExplainStmt *stmt); extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, - struct ExplainState *es, ParseState *pstate, + ExplainState *es, ParseState *pstate, ParamListInfo params); extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, - struct ExplainState *es, const char *queryString, + ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage, const MemoryContextCounters *mem_counters); -extern void ExplainPrintPlan(struct ExplainState *es, QueryDesc *queryDesc); -extern void ExplainPrintTriggers(struct ExplainState *es, +extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); +extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc); -extern void ExplainPrintJITSummary(struct ExplainState *es, +extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc); -extern void ExplainQueryText(struct ExplainState *es, QueryDesc *queryDesc); -extern void ExplainQueryParameters(struct ExplainState *es, +extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc); +extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen); #endif /* EXPLAIN_H */ diff --git a/src/include/commands/explain_dr.h b/src/include/commands/explain_dr.h index 55da63d66bdf6..b62f1f8542cb5 100644 --- a/src/include/commands/explain_dr.h +++ b/src/include/commands/explain_dr.h @@ -16,7 +16,8 @@ #include "executor/instrument.h" #include "tcop/dest.h" -struct ExplainState; /* avoid including explain.h here */ +/* avoid including explain_state.h here */ +typedef struct ExplainState ExplainState; /* Instrumentation data for EXPLAIN's SERIALIZE option */ typedef struct SerializeMetrics @@ -26,7 +27,7 @@ typedef struct SerializeMetrics BufferUsage bufferUsage; /* buffers accessed during serialization */ } SerializeMetrics; -extern DestReceiver *CreateExplainSerializeDestReceiver(struct ExplainState *es); +extern DestReceiver *CreateExplainSerializeDestReceiver(ExplainState *es); extern SerializeMetrics GetSerializationMetrics(DestReceiver *dest); #endif diff --git a/src/include/commands/explain_format.h b/src/include/commands/explain_format.h index 05045bf8cb4af..f849a3938ce99 100644 --- a/src/include/commands/explain_format.h +++ b/src/include/commands/explain_format.h @@ -15,44 +15,45 @@ #include "nodes/pg_list.h" -struct ExplainState; /* avoid including explain.h here */ +/* avoid including explain_state.h here */ +typedef struct ExplainState ExplainState; extern void ExplainPropertyList(const char *qlabel, List *data, - struct ExplainState *es); + ExplainState *es); extern void ExplainPropertyListNested(const char *qlabel, List *data, - struct ExplainState *es); + ExplainState *es); extern void ExplainPropertyText(const char *qlabel, const char *value, - struct ExplainState *es); + ExplainState *es); extern void ExplainPropertyInteger(const char *qlabel, const char *unit, - int64 value, struct ExplainState *es); + int64 value, ExplainState *es); extern void ExplainPropertyUInteger(const char *qlabel, const char *unit, - uint64 value, struct ExplainState *es); + uint64 value, ExplainState *es); extern void ExplainPropertyFloat(const char *qlabel, const char *unit, double value, int ndigits, - struct ExplainState *es); + ExplainState *es); extern void ExplainPropertyBool(const char *qlabel, bool value, - struct ExplainState *es); + ExplainState *es); extern void ExplainOpenGroup(const char *objtype, const char *labelname, - bool labeled, struct ExplainState *es); + bool labeled, ExplainState *es); extern void ExplainCloseGroup(const char *objtype, const char *labelname, - bool labeled, struct ExplainState *es); + bool labeled, ExplainState *es); extern void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, bool labeled, int depth, - struct ExplainState *es); -extern void ExplainSaveGroup(struct ExplainState *es, int depth, + ExplainState *es); +extern void ExplainSaveGroup(ExplainState *es, int depth, int *state_save); -extern void ExplainRestoreGroup(struct ExplainState *es, int depth, +extern void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save); extern void ExplainDummyGroup(const char *objtype, const char *labelname, - struct ExplainState *es); + ExplainState *es); -extern void ExplainBeginOutput(struct ExplainState *es); -extern void ExplainEndOutput(struct ExplainState *es); -extern void ExplainSeparatePlans(struct ExplainState *es); +extern void ExplainBeginOutput(ExplainState *es); +extern void ExplainEndOutput(ExplainState *es); +extern void ExplainSeparatePlans(ExplainState *es); -extern void ExplainIndentText(struct ExplainState *es); +extern void ExplainIndentText(ExplainState *es); #endif diff --git a/src/include/commands/explain_state.h b/src/include/commands/explain_state.h index 32728f5d1a175..ba073b86918d5 100644 --- a/src/include/commands/explain_state.h +++ b/src/include/commands/explain_state.h @@ -79,7 +79,7 @@ typedef struct ExplainState typedef void (*ExplainOptionHandler) (ExplainState *, DefElem *, ParseState *); /* Hook to perform additional EXPLAIN options validation */ -typedef void (*explain_validate_options_hook_type) (struct ExplainState *es, List *options, +typedef void (*explain_validate_options_hook_type) (ExplainState *es, List *options, ParseState *pstate); extern PGDLLIMPORT explain_validate_options_hook_type explain_validate_options_hook; diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index b4da4e6a16aac..fcd7e7027f3b6 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -16,8 +16,8 @@ #include "nodes/execnodes.h" #include "nodes/pathnodes.h" -/* To avoid including explain.h here, reference ExplainState thus: */ -struct ExplainState; +/* avoid including explain_state.h here */ +typedef struct ExplainState ExplainState; /* @@ -137,16 +137,16 @@ typedef void (*RefetchForeignRow_function) (EState *estate, bool *updated); typedef void (*ExplainForeignScan_function) (ForeignScanState *node, - struct ExplainState *es); + ExplainState *es); typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, - struct ExplainState *es); + ExplainState *es); typedef void (*ExplainDirectModify_function) (ForeignScanState *node, - struct ExplainState *es); + ExplainState *es); typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel, HeapTuple *rows, int targrows, From d4d1fc527bdb333d818038081c17ed7d9b1697c1 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 10:48:30 +0200 Subject: [PATCH 69/96] Update various forward declarations to use typedef There are a number of forward declarations that use struct but not the customary typedef, because that could have led to repeat typedefs, which was not allowed. This is now allowed in C11, so we can update these to provide the typedefs as well, so that the later uses of the types look more consistent. Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- src/include/commands/tablecmds.h | 5 ++-- src/include/common/string.h | 7 +++-- src/include/executor/executor.h | 4 +-- src/include/executor/tablefunc.h | 18 ++++++------ src/include/nodes/execnodes.h | 40 +++++++++++++-------------- src/include/nodes/nodeFuncs.h | 6 ++-- src/include/nodes/params.h | 15 +++++----- src/include/nodes/subscripting.h | 14 +++++----- src/include/nodes/supportnodes.h | 26 ++++++++--------- src/include/optimizer/optimizer.h | 14 +++++----- src/include/parser/parse_utilcmd.h | 4 +-- src/include/partitioning/partbounds.h | 6 ++-- src/include/partitioning/partprune.h | 10 +++---- src/include/rewrite/rewriteManip.h | 4 +-- src/include/storage/bufmgr.h | 24 ++++++++-------- src/include/storage/bulk_write.h | 4 +-- src/include/storage/dsm.h | 4 +-- src/include/storage/shmem.h | 5 ++-- src/include/tcop/pquery.h | 4 +-- src/include/utils/array.h | 6 ++-- src/include/utils/lsyscache.h | 6 ++-- src/include/utils/plancache.h | 14 +++++----- src/include/utils/ruleutils.h | 8 +++--- 23 files changed, 124 insertions(+), 124 deletions(-) diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 6832470d38729..e9b0fab0767b4 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -21,7 +21,8 @@ #include "storage/lock.h" #include "utils/relcache.h" -struct AlterTableUtilityContext; /* avoid including tcop/utility.h here */ +typedef struct AlterTableUtilityContext AlterTableUtilityContext; /* avoid including + * tcop/utility.h here */ extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, @@ -34,7 +35,7 @@ extern void RemoveRelations(DropStmt *drop); extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, - struct AlterTableUtilityContext *context); + AlterTableUtilityContext *context); extern LOCKMODE AlterTableGetLockLevel(List *cmds); diff --git a/src/include/common/string.h b/src/include/common/string.h index ffe5ed51c5db6..55ed8774364ba 100644 --- a/src/include/common/string.h +++ b/src/include/common/string.h @@ -12,7 +12,8 @@ #include -struct StringInfoData; /* avoid including stringinfo.h here */ +typedef struct StringInfoData *StringInfo; /* avoid including stringinfo.h + * here */ typedef struct PromptInterruptContext { @@ -32,8 +33,8 @@ extern bool pg_is_ascii(const char *str); /* functions in src/common/pg_get_line.c */ extern char *pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx); -extern bool pg_get_line_buf(FILE *stream, struct StringInfoData *buf); -extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf, +extern bool pg_get_line_buf(FILE *stream, StringInfo buf); +extern bool pg_get_line_append(FILE *stream, StringInfo buf, PromptInterruptContext *prompt_ctx); /* functions in src/common/sprompt.c */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 31133514e8438..3248e78cd28fa 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -100,12 +100,12 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook; /* * prototypes from functions in execAmi.c */ -struct Path; /* avoid including pathnodes.h here */ +typedef struct Path Path; /* avoid including pathnodes.h here */ extern void ExecReScan(PlanState *node); extern void ExecMarkPos(PlanState *node); extern void ExecRestrPos(PlanState *node); -extern bool ExecSupportsMarkRestore(struct Path *pathnode); +extern bool ExecSupportsMarkRestore(Path *pathnode); extern bool ExecSupportsBackwardScan(Plan *node); extern bool ExecMaterializesOutput(NodeTag plantype); diff --git a/src/include/executor/tablefunc.h b/src/include/executor/tablefunc.h index 2c4498c5955e7..4dd5fef4aead0 100644 --- a/src/include/executor/tablefunc.h +++ b/src/include/executor/tablefunc.h @@ -14,7 +14,7 @@ #define _TABLEFUNC_H /* Forward-declare this to avoid including execnodes.h here */ -struct TableFuncScanState; +typedef struct TableFuncScanState TableFuncScanState; /* * TableFuncRoutine holds function pointers used for generating content of @@ -51,17 +51,17 @@ struct TableFuncScanState; */ typedef struct TableFuncRoutine { - void (*InitOpaque) (struct TableFuncScanState *state, int natts); - void (*SetDocument) (struct TableFuncScanState *state, Datum value); - void (*SetNamespace) (struct TableFuncScanState *state, const char *name, + void (*InitOpaque) (TableFuncScanState *state, int natts); + void (*SetDocument) (TableFuncScanState *state, Datum value); + void (*SetNamespace) (TableFuncScanState *state, const char *name, const char *uri); - void (*SetRowFilter) (struct TableFuncScanState *state, const char *path); - void (*SetColumnFilter) (struct TableFuncScanState *state, + void (*SetRowFilter) (TableFuncScanState *state, const char *path); + void (*SetColumnFilter) (TableFuncScanState *state, const char *path, int colnum); - bool (*FetchRow) (struct TableFuncScanState *state); - Datum (*GetValue) (struct TableFuncScanState *state, int colnum, + bool (*FetchRow) (TableFuncScanState *state); + Datum (*GetValue) (TableFuncScanState *state, int colnum, Oid typid, int32 typmod, bool *isnull); - void (*DestroyOpaque) (struct TableFuncScanState *state); + void (*DestroyOpaque) (TableFuncScanState *state); } TableFuncRoutine; #endif /* _TABLEFUNC_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 71857feae4823..3a920cc7d17a4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -49,15 +49,13 @@ #include "utils/tuplesort.h" #include "utils/tuplestore.h" -struct PlanState; /* forward references in this file */ -struct ParallelHashJoinState; -struct ExecRowMark; -struct ExprState; -struct ExprContext; -struct RangeTblEntry; /* avoid including parsenodes.h here */ -struct ExprEvalStep; /* avoid including execExpr.h everywhere */ -struct CopyMultiInsertBuffer; -struct LogicalTapeSet; +/* + * forward references in this file + */ +typedef struct PlanState PlanState; +typedef struct ExecRowMark ExecRowMark; +typedef struct ExprState ExprState; +typedef struct ExprContext ExprContext; /* ---------------- @@ -67,8 +65,8 @@ struct LogicalTapeSet; * It contains instructions (in ->steps) to evaluate the expression. * ---------------- */ -typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression, - struct ExprContext *econtext, +typedef Datum (*ExprStateEvalFunc) (ExprState *expression, + ExprContext *econtext, bool *isNull); /* Bits in ExprState->flags (see also execExpr.h for private flag bits): */ @@ -131,7 +129,7 @@ typedef struct ExprState int steps_alloc; /* allocated length of steps array */ #define FIELDNO_EXPRSTATE_PARENT 11 - struct PlanState *parent; /* parent PlanState node, if any */ + PlanState *parent; /* parent PlanState node, if any */ ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */ Datum *innermost_caseval; @@ -638,8 +636,8 @@ typedef struct ResultRelInfo */ typedef struct AsyncRequest { - struct PlanState *requestor; /* Node that wants a tuple */ - struct PlanState *requestee; /* Node from which a tuple is wanted */ + PlanState *requestor; /* Node that wants a tuple */ + PlanState *requestee; /* Node from which a tuple is wanted */ int request_index; /* Scratch space for requestor */ bool callback_pending; /* Callback is needed */ bool request_complete; /* Request complete, result valid */ @@ -665,8 +663,8 @@ typedef struct EState Index es_range_table_size; /* size of the range table arrays */ Relation *es_relations; /* Array of per-range-table-entry Relation * pointers, or NULL if not yet opened */ - struct ExecRowMark **es_rowmarks; /* Array of per-range-table-entry - * ExecRowMarks, or NULL if none */ + ExecRowMark **es_rowmarks; /* Array of per-range-table-entry + * ExecRowMarks, or NULL if none */ List *es_rteperminfos; /* List of RTEPermissionInfo */ PlannedStmt *es_plannedstmt; /* link to top of plan tree */ List *es_part_prune_infos; /* List of PartitionPruneInfo */ @@ -1006,8 +1004,8 @@ typedef struct SubPlanState { NodeTag type; SubPlan *subplan; /* expression plan node */ - struct PlanState *planstate; /* subselect plan's state tree */ - struct PlanState *parent; /* parent plan node's state tree */ + PlanState *planstate; /* subselect plan's state tree */ + PlanState *parent; /* parent plan node's state tree */ ExprState *testexpr; /* state of combining expression */ HeapTuple curTuple; /* copy of most recent tuple from subplan */ Datum curArray; /* most recent array from ARRAY() subplan */ @@ -1143,7 +1141,7 @@ typedef struct JsonExprState * if no more tuples are available. * ---------------- */ -typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate); +typedef TupleTableSlot *(*ExecProcNodeMtd) (PlanState *pstate); /* ---------------- * PlanState node @@ -1180,8 +1178,8 @@ typedef struct PlanState * subPlan list, which does not exist in the plan tree). */ ExprState *qual; /* boolean qual condition */ - struct PlanState *lefttree; /* input plan tree(s) */ - struct PlanState *righttree; + PlanState *lefttree; /* input plan tree(s) */ + PlanState *righttree; List *initPlan; /* Init SubPlanState nodes (un-correlated expr * subselects) */ diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h index 5653fec8cbe5e..11c1c140ec4d0 100644 --- a/src/include/nodes/nodeFuncs.h +++ b/src/include/nodes/nodeFuncs.h @@ -15,7 +15,7 @@ #include "nodes/parsenodes.h" -struct PlanState; /* avoid including execnodes.h too */ +typedef struct PlanState PlanState; /* avoid including execnodes.h too */ /* flags bits for query_tree_walker and query_tree_mutator */ @@ -38,7 +38,7 @@ typedef bool (*check_function_callback) (Oid func_id, void *context); /* callback functions for tree walkers */ typedef bool (*tree_walker_callback) (Node *node, void *context); -typedef bool (*planstate_tree_walker_callback) (struct PlanState *planstate, +typedef bool (*planstate_tree_walker_callback) (PlanState *planstate, void *context); /* callback functions for tree mutators */ @@ -217,7 +217,7 @@ extern bool raw_expression_tree_walker_impl(Node *node, tree_walker_callback walker, void *context); -extern bool planstate_tree_walker_impl(struct PlanState *planstate, +extern bool planstate_tree_walker_impl(PlanState *planstate, planstate_tree_walker_callback walker, void *context); diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h index 4321ca6329bf4..ca4117b14496e 100644 --- a/src/include/nodes/params.h +++ b/src/include/nodes/params.h @@ -14,11 +14,10 @@ #ifndef PARAMS_H #define PARAMS_H -/* Forward declarations, to avoid including other headers */ -struct Bitmapset; -struct ExprState; -struct Param; -struct ParseState; +/* to avoid including other headers */ +typedef struct ExprState ExprState; +typedef struct Param Param; +typedef struct ParseState ParseState; /* @@ -101,11 +100,11 @@ typedef ParamExternData *(*ParamFetchHook) (ParamListInfo params, int paramid, bool speculative, ParamExternData *workspace); -typedef void (*ParamCompileHook) (ParamListInfo params, struct Param *param, - struct ExprState *state, +typedef void (*ParamCompileHook) (ParamListInfo params, Param *param, + ExprState *state, Datum *resv, bool *resnull); -typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg); +typedef void (*ParserSetupHook) (ParseState *pstate, void *arg); typedef struct ParamListInfoData { diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h index 234e8ad80120c..e991f4bf8265f 100644 --- a/src/include/nodes/subscripting.h +++ b/src/include/nodes/subscripting.h @@ -15,10 +15,10 @@ #include "nodes/primnodes.h" -/* Forward declarations, to avoid including other headers */ -struct ParseState; -struct SubscriptingRefState; -struct SubscriptExecSteps; +/* to avoid including other headers */ +typedef struct ParseState ParseState; +typedef struct SubscriptingRefState SubscriptingRefState; +typedef struct SubscriptExecSteps SubscriptExecSteps; /* * The SQL-visible function that defines a subscripting method is declared @@ -94,7 +94,7 @@ struct SubscriptExecSteps; */ typedef void (*SubscriptTransform) (SubscriptingRef *sbsref, List *indirection, - struct ParseState *pstate, + ParseState *pstate, bool isSlice, bool isAssignment); @@ -151,8 +151,8 @@ typedef void (*SubscriptTransform) (SubscriptingRef *sbsref, * Set the relevant pointers to NULL for any omitted methods. */ typedef void (*SubscriptExecSetup) (const SubscriptingRef *sbsref, - struct SubscriptingRefState *sbsrefstate, - struct SubscriptExecSteps *methods); + SubscriptingRefState *sbsrefstate, + SubscriptExecSteps *methods); /* Struct returned by the SQL-visible subscript handler function */ typedef struct SubscriptRoutines diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 9c047cc401bec..7b623d5405874 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -35,10 +35,10 @@ #include "nodes/plannodes.h" -struct PlannerInfo; /* avoid including pathnodes.h here */ -struct IndexOptInfo; -struct SpecialJoinInfo; -struct WindowClause; +typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */ +typedef struct IndexOptInfo IndexOptInfo; +typedef struct SpecialJoinInfo SpecialJoinInfo; +typedef struct WindowClause WindowClause; /* * The Simplify request allows the support function to perform plan-time @@ -65,7 +65,7 @@ typedef struct SupportRequestSimplify { NodeTag type; - struct PlannerInfo *root; /* Planner's infrastructure */ + PlannerInfo *root; /* Planner's infrastructure */ FuncExpr *fcall; /* Function call to be simplified */ } SupportRequestSimplify; @@ -93,14 +93,14 @@ typedef struct SupportRequestSelectivity NodeTag type; /* Input fields: */ - struct PlannerInfo *root; /* Planner's infrastructure */ + PlannerInfo *root; /* Planner's infrastructure */ Oid funcid; /* function we are inquiring about */ List *args; /* pre-simplified arguments to function */ Oid inputcollid; /* function's input collation */ bool is_join; /* is this a join or restriction case? */ int varRelid; /* if restriction, RTI of target relation */ JoinType jointype; /* if join, outer join type */ - struct SpecialJoinInfo *sjinfo; /* if outer join, info about join */ + SpecialJoinInfo *sjinfo; /* if outer join, info about join */ /* Output fields: */ Selectivity selectivity; /* returned selectivity estimate */ @@ -133,7 +133,7 @@ typedef struct SupportRequestCost NodeTag type; /* Input fields: */ - struct PlannerInfo *root; /* Planner's infrastructure (could be NULL) */ + PlannerInfo *root; /* Planner's infrastructure (could be NULL) */ Oid funcid; /* function we are inquiring about */ Node *node; /* parse node invoking function, or NULL */ @@ -160,7 +160,7 @@ typedef struct SupportRequestRows NodeTag type; /* Input fields: */ - struct PlannerInfo *root; /* Planner's infrastructure (could be NULL) */ + PlannerInfo *root; /* Planner's infrastructure (could be NULL) */ Oid funcid; /* function we are inquiring about */ Node *node; /* parse node invoking function */ @@ -225,11 +225,11 @@ typedef struct SupportRequestIndexCondition NodeTag type; /* Input fields: */ - struct PlannerInfo *root; /* Planner's infrastructure */ + PlannerInfo *root; /* Planner's infrastructure */ Oid funcid; /* function we are inquiring about */ Node *node; /* parse node invoking function */ int indexarg; /* index of function arg matching indexcol */ - struct IndexOptInfo *index; /* planner's info about target index */ + IndexOptInfo *index; /* planner's info about target index */ int indexcol; /* index of target index column (0-based) */ Oid opfamily; /* index column's operator family */ Oid indexcollation; /* index column's collation */ @@ -293,7 +293,7 @@ typedef struct SupportRequestWFuncMonotonic /* Input fields: */ WindowFunc *window_func; /* Pointer to the window function data */ - struct WindowClause *window_clause; /* Pointer to the window clause data */ + WindowClause *window_clause; /* Pointer to the window clause data */ /* Output fields: */ MonotonicFunction monotonic; @@ -336,7 +336,7 @@ typedef struct SupportRequestOptimizeWindowClause /* Input fields: */ WindowFunc *window_func; /* Pointer to the window function data */ - struct WindowClause *window_clause; /* Pointer to the window clause data */ + WindowClause *window_clause; /* Pointer to the window clause data */ /* Input/Output fields: */ int frameOptions; /* New frameOptions, or left untouched if no diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index a42abac01bf0b..04878f1f1c224 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -35,9 +35,9 @@ typedef struct IndexOptInfo IndexOptInfo; typedef struct SpecialJoinInfo SpecialJoinInfo; /* It also seems best not to include plannodes.h, params.h, or htup.h here */ -struct PlannedStmt; -struct ParamListInfoData; -struct HeapTupleData; +typedef struct PlannedStmt PlannedStmt; +typedef struct ParamListInfoData *ParamListInfo; +typedef struct HeapTupleData *HeapTuple; /* in path/clausesel.c: */ @@ -102,9 +102,9 @@ extern PGDLLIMPORT int debug_parallel_query; extern PGDLLIMPORT bool parallel_leader_participation; extern PGDLLIMPORT bool enable_distinct_reordering; -extern struct PlannedStmt *planner(Query *parse, const char *query_string, - int cursorOptions, - struct ParamListInfoData *boundParams); +extern PlannedStmt *planner(Query *parse, const char *query_string, + int cursorOptions, + ParamListInfo boundParams); extern Expr *expression_planner(Expr *expr); extern Expr *expression_planner_with_deps(Expr *expr, @@ -147,7 +147,7 @@ extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info); extern List *expand_function_arguments(List *args, bool include_out_arguments, Oid result_type, - struct HeapTupleData *func_tuple); + HeapTuple func_tuple); extern ScalarArrayOpExpr *make_SAOP_expr(Oid oper, Node *leftexpr, Oid coltype, Oid arraycollid, diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de79741..4965fac4495e2 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -16,7 +16,7 @@ #include "parser/parse_node.h" -struct AttrMap; /* avoid including attmap.h here */ +typedef struct AttrMap AttrMap; /* avoid including attmap.h here */ extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString); @@ -38,7 +38,7 @@ extern List *expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause); extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, - const struct AttrMap *attmap, + const AttrMap *attmap, Oid *constraintOid); #endif /* PARSE_UTILCMD_H */ diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index 65f161f7188c4..083b6e3a88a20 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -15,7 +15,7 @@ #include "parser/parse_node.h" #include "partitioning/partdefs.h" -struct RelOptInfo; /* avoid including pathnodes.h here */ +typedef struct RelOptInfo RelOptInfo; /* avoid including pathnodes.h here */ /* @@ -114,8 +114,8 @@ extern PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src, extern PartitionBoundInfo partition_bounds_merge(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation, - struct RelOptInfo *outer_rel, - struct RelOptInfo *inner_rel, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel, JoinType jointype, List **outer_parts, List **inner_parts); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index c413734789a97..657b436d9588d 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -17,8 +17,8 @@ #include "nodes/execnodes.h" #include "partitioning/partdefs.h" -struct PlannerInfo; /* avoid including pathnodes.h here */ -struct RelOptInfo; +typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */ +typedef struct RelOptInfo RelOptInfo; /* @@ -70,11 +70,11 @@ typedef struct PartitionPruneContext #define PruneCxtStateIdx(partnatts, step_id, keyno) \ ((partnatts) * (step_id) + (keyno)) -extern int make_partition_pruneinfo(struct PlannerInfo *root, - struct RelOptInfo *parentrel, +extern int make_partition_pruneinfo(PlannerInfo *root, + RelOptInfo *parentrel, List *subpaths, List *prunequal); -extern Bitmapset *prune_append_rel_partitions(struct RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index 7c018f2a4e358..74de195deeb2a 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -17,7 +17,7 @@ #include "nodes/parsenodes.h" #include "nodes/pathnodes.h" -struct AttrMap; /* avoid including attmap.h here */ +typedef struct AttrMap AttrMap; /* avoid including attmap.h here */ typedef struct replace_rte_variables_context replace_rte_variables_context; @@ -101,7 +101,7 @@ extern Node *replace_rte_variables_mutator(Node *node, extern Node *map_variable_attnos(Node *node, int target_varno, int sublevels_up, - const struct AttrMap *attno_map, + const AttrMap *attno_map, Oid to_rowtype, bool *found_whole_row); extern Node *ReplaceVarFromTargetList(Var *var, diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 41fdc1e76938e..47360a3d3d85c 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -93,6 +93,9 @@ typedef enum ExtendBufferedFlags EB_LOCK_TARGET = (1 << 5), } ExtendBufferedFlags; +/* forward declared, to avoid including smgr.h here */ +typedef struct SMgrRelationData *SMgrRelation; + /* * Some functions identify relations either by relation or smgr + * relpersistence. Used via the BMR_REL()/BMR_SMGR() macros below. This @@ -101,7 +104,7 @@ typedef enum ExtendBufferedFlags typedef struct BufferManagerRelation { Relation rel; - struct SMgrRelationData *smgr; + SMgrRelation smgr; char relpersistence; } BufferManagerRelation; @@ -122,7 +125,7 @@ struct ReadBuffersOperation { /* The following members should be set by the caller. */ Relation rel; /* optional */ - struct SMgrRelationData *smgr; + SMgrRelation smgr; char persistence; ForkNumber forknum; BufferAccessStrategy strategy; @@ -143,11 +146,8 @@ struct ReadBuffersOperation typedef struct ReadBuffersOperation ReadBuffersOperation; -/* forward declared, to avoid having to expose buf_internals.h here */ -struct WritebackContext; - -/* forward declared, to avoid including smgr.h here */ -struct SMgrRelationData; +/* to avoid having to expose buf_internals.h here */ +typedef struct WritebackContext WritebackContext; /* in globals.c ... this duplicates miscadmin.h */ extern PGDLLIMPORT int NBuffers; @@ -201,7 +201,7 @@ extern PGDLLIMPORT int32 *LocalRefCount; /* * prototypes for functions in bufmgr.c */ -extern PrefetchBufferResult PrefetchSharedBuffer(struct SMgrRelationData *smgr_reln, +extern PrefetchBufferResult PrefetchSharedBuffer(SMgrRelation smgr_reln, ForkNumber forkNum, BlockNumber blockNum); extern PrefetchBufferResult PrefetchBuffer(Relation reln, ForkNumber forkNum, @@ -268,15 +268,15 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum); extern void FlushOneBuffer(Buffer buffer); extern void FlushRelationBuffers(Relation rel); -extern void FlushRelationsAllBuffers(struct SMgrRelationData **smgrs, int nrels); +extern void FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels); extern void CreateAndCopyRelationData(RelFileLocator src_rlocator, RelFileLocator dst_rlocator, bool permanent); extern void FlushDatabaseBuffers(Oid dbid); -extern void DropRelationBuffers(struct SMgrRelationData *smgr_reln, +extern void DropRelationBuffers(SMgrRelation smgr_reln, ForkNumber *forkNum, int nforks, BlockNumber *firstDelBlock); -extern void DropRelationsAllBuffers(struct SMgrRelationData **smgr_reln, +extern void DropRelationsAllBuffers(SMgrRelation *smgr_reln, int nlocators); extern void DropDatabaseBuffers(Oid dbid); @@ -298,7 +298,7 @@ extern bool ConditionalLockBufferForCleanup(Buffer buffer); extern bool IsBufferCleanupOK(Buffer buffer); extern bool HoldingBufferPinThatDelaysRecovery(void); -extern bool BgBufferSync(struct WritebackContext *wb_context); +extern bool BgBufferSync(WritebackContext *wb_context); extern uint32 GetPinLimit(void); extern uint32 GetLocalPinLimit(void); diff --git a/src/include/storage/bulk_write.h b/src/include/storage/bulk_write.h index 7885415f6cb8f..ca359784016c4 100644 --- a/src/include/storage/bulk_write.h +++ b/src/include/storage/bulk_write.h @@ -28,10 +28,10 @@ typedef struct BulkWriteState BulkWriteState; typedef PGIOAlignedBlock *BulkWriteBuffer; /* forward declared from smgr.h */ -struct SMgrRelationData; +typedef struct SMgrRelationData *SMgrRelation; extern BulkWriteState *smgr_bulk_start_rel(Relation rel, ForkNumber forknum); -extern BulkWriteState *smgr_bulk_start_smgr(struct SMgrRelationData *smgr, ForkNumber forknum, bool use_wal); +extern BulkWriteState *smgr_bulk_start_smgr(SMgrRelation smgr, ForkNumber forknum, bool use_wal); extern BulkWriteBuffer smgr_bulk_get_buf(BulkWriteState *bulkstate); extern void smgr_bulk_write(BulkWriteState *bulkstate, BlockNumber blocknum, BulkWriteBuffer buf, bool page_std); diff --git a/src/include/storage/dsm.h b/src/include/storage/dsm.h index 2302cc7f40b4b..88cf0962957ec 100644 --- a/src/include/storage/dsm.h +++ b/src/include/storage/dsm.h @@ -20,9 +20,9 @@ typedef struct dsm_segment dsm_segment; #define DSM_CREATE_NULL_IF_MAXSEGMENTS 0x0001 /* Startup and shutdown functions. */ -struct PGShmemHeader; /* avoid including pg_shmem.h */ +typedef struct PGShmemHeader PGShmemHeader; /* avoid including pg_shmem.h */ extern void dsm_cleanup_using_control_segment(dsm_handle old_control_handle); -extern void dsm_postmaster_startup(struct PGShmemHeader *); +extern void dsm_postmaster_startup(PGShmemHeader *); extern void dsm_backend_shutdown(void); extern void dsm_detach_all(void); diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 8604feca93ba0..cd683a9d2d93d 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -27,8 +27,9 @@ /* shmem.c */ extern PGDLLIMPORT slock_t *ShmemLock; -struct PGShmemHeader; /* avoid including storage/pg_shmem.h here */ -extern void InitShmemAccess(struct PGShmemHeader *seghdr); +typedef struct PGShmemHeader PGShmemHeader; /* avoid including + * storage/pg_shmem.h here */ +extern void InitShmemAccess(PGShmemHeader *seghdr); extern void InitShmemAllocation(void); extern void *ShmemAlloc(Size size); extern void *ShmemAllocNoError(Size size); diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index fa3cc5f2dfcaf..ccd995fc88e7f 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -17,7 +17,7 @@ #include "nodes/parsenodes.h" #include "utils/portal.h" -struct PlannedStmt; /* avoid including plannodes.h here */ +typedef struct PlannedStmt PlannedStmt; /* avoid including plannodes.h here */ extern PGDLLIMPORT Portal ActivePortal; @@ -44,7 +44,7 @@ extern uint64 PortalRunFetch(Portal portal, long count, DestReceiver *dest); -extern bool PlannedStmtRequiresSnapshot(struct PlannedStmt *pstmt); +extern bool PlannedStmtRequiresSnapshot(PlannedStmt *pstmt); extern void EnsurePortalSnapshotExists(void); diff --git a/src/include/utils/array.h b/src/include/utils/array.h index 52f1fbf8d43f6..3383f16a3bb61 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -65,8 +65,8 @@ #include "utils/expandeddatum.h" /* avoid including execnodes.h here */ -struct ExprState; -struct ExprContext; +typedef struct ExprState ExprState; +typedef struct ExprContext ExprContext; /* @@ -384,7 +384,7 @@ extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx, int arraytyplen, int elmlen, bool elmbyval, char elmalign); extern Datum array_map(Datum arrayd, - struct ExprState *exprstate, struct ExprContext *econtext, + ExprState *exprstate, ExprContext *econtext, Oid retType, ArrayMapState *amstate); extern void array_bitmap_copy(bits8 *destbitmap, int destoffset, diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index c65cee4f24cd2..50fb149e9ac92 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -19,7 +19,7 @@ #include "nodes/pg_list.h" /* avoid including subscripting.h here */ -struct SubscriptRoutines; +typedef struct SubscriptRoutines SubscriptRoutines; /* Result list element for get_op_index_interpretation */ typedef struct OpIndexInterpretation @@ -187,8 +187,8 @@ extern Oid get_typmodin(Oid typid); extern Oid get_typcollation(Oid typid); extern bool type_is_collatable(Oid typid); extern RegProcedure get_typsubscript(Oid typid, Oid *typelemp); -extern const struct SubscriptRoutines *getSubscriptingRoutines(Oid typid, - Oid *typelemp); +extern const SubscriptRoutines *getSubscriptingRoutines(Oid typid, + Oid *typelemp); extern Oid getBaseType(Oid typid); extern Oid getBaseTypeAndTypmod(Oid typid, int32 *typmod); extern int32 get_typavgwidth(Oid typid, int32 typmod); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 1baa6d50bfd7a..a82b66d4bc22e 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -24,8 +24,8 @@ /* Forward declarations, to avoid including parsenodes.h here */ -struct Query; -struct RawStmt; +typedef struct Query Query; +typedef struct RawStmt RawStmt; /* possible values for plan_cache_mode */ typedef enum @@ -105,8 +105,8 @@ typedef void (*PostRewriteHook) (List *querytree_list, void *arg); typedef struct CachedPlanSource { int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ - struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */ - struct Query *analyzed_parse_tree; /* analyzed parse tree, or NULL */ + RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */ + Query *analyzed_parse_tree; /* analyzed parse tree, or NULL */ const char *query_string; /* source text of query */ CommandTag commandTag; /* command tag for query */ Oid *param_types; /* array of parameter type OIDs, or NULL */ @@ -202,13 +202,13 @@ extern void ResetPlanCache(void); extern void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner); -extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree, +extern CachedPlanSource *CreateCachedPlan(RawStmt *raw_parse_tree, const char *query_string, CommandTag commandTag); -extern CachedPlanSource *CreateCachedPlanForQuery(struct Query *analyzed_parse_tree, +extern CachedPlanSource *CreateCachedPlanForQuery(Query *analyzed_parse_tree, const char *query_string, CommandTag commandTag); -extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree, +extern CachedPlanSource *CreateOneShotCachedPlan(RawStmt *raw_parse_tree, const char *query_string, CommandTag commandTag); extern void CompleteCachedPlan(CachedPlanSource *plansource, diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 5f2ea2e4d0ebf..7ba7d8879149c 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -17,8 +17,8 @@ #include "nodes/parsenodes.h" #include "nodes/pg_list.h" -struct Plan; /* avoid including plannodes.h here */ -struct PlannedStmt; +typedef struct Plan Plan; /* avoid including plannodes.h here */ +typedef struct PlannedStmt PlannedStmt; /* Flags for pg_get_indexdef_columns_extended() */ #define RULE_INDEXDEF_PRETTY 0x01 @@ -37,10 +37,10 @@ extern char *pg_get_constraintdef_command(Oid constraintId); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); -extern List *deparse_context_for_plan_tree(struct PlannedStmt *pstmt, +extern List *deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names); extern List *set_deparse_context_plan(List *dpcontext, - struct Plan *plan, List *ancestors); + Plan *plan, List *ancestors); extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *get_window_frame_options_for_explain(int frameOptions, From dc41d7415fc6eea3b5c5528a39d07016983ea75e Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 10:48:30 +0200 Subject: [PATCH 70/96] Remove hbaPort type This was just a workaround to avoid including the header file that defines the Port type. With C11, we can now just re-define the Port type without the possibility of a conflict. Reviewed-by: Tom Lane Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- src/backend/libpq/auth.c | 8 ++++---- src/backend/libpq/hba.c | 6 +++--- src/include/libpq/hba.h | 6 +++--- src/tools/pgindent/typedefs.list | 1 - 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 4da46666439db..ec4dbacf01552 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -70,14 +70,14 @@ static int CheckMD5Auth(Port *port, char *shadow_pass, /* Standard TCP port number for Ident service. Assigned by IANA */ #define IDENT_PORT 113 -static int ident_inet(hbaPort *port); +static int ident_inet(Port *port); /*---------------------------------------------------------------- * Peer authentication *---------------------------------------------------------------- */ -static int auth_peer(hbaPort *port); +static int auth_peer(Port *port); /*---------------------------------------------------------------- @@ -1668,7 +1668,7 @@ interpret_ident_response(const char *ident_response, * latch was set would improve the responsiveness to timeouts/cancellations. */ static int -ident_inet(hbaPort *port) +ident_inet(Port *port) { const SockAddr remote_addr = port->raddr; const SockAddr local_addr = port->laddr; @@ -1853,7 +1853,7 @@ ident_inet(hbaPort *port) * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR. */ static int -auth_peer(hbaPort *port) +auth_peer(Port *port) { uid_t uid; gid_t gid; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index fecee8224d075..97a3586000bb2 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1075,7 +1075,7 @@ hostname_match(const char *pattern, const char *actual_hostname) * Check to see if a connecting IP matches a given host name. */ static bool -check_hostname(hbaPort *port, const char *hostname) +check_hostname(Port *port, const char *hostname) { struct addrinfo *gai_result, *gai; @@ -2528,7 +2528,7 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, * request. */ static void -check_hba(hbaPort *port) +check_hba(Port *port) { Oid roleid; ListCell *line; @@ -3125,7 +3125,7 @@ load_ident(void) * method = uaImplicitReject. */ void -hba_getauthmethod(hbaPort *port) +hba_getauthmethod(Port *port) { check_hba(port); } diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 3657f182db3e3..e3748d3c8c963 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -169,13 +169,13 @@ typedef struct TokenizedAuthLine char *err_msg; /* Error message if any */ } TokenizedAuthLine; -/* kluge to avoid including libpq/libpq-be.h here */ -typedef struct Port hbaPort; +/* avoid including libpq/libpq-be.h here */ +typedef struct Port Port; extern bool load_hba(void); extern bool load_ident(void); extern const char *hba_authname(UserAuth auth_method); -extern void hba_getauthmethod(hbaPort *port); +extern void hba_getauthmethod(Port *port); extern int check_usermap(const char *usermap_name, const char *pg_user, const char *system_user, bool case_insensitive); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index a13e816289023..9db8e2424c038 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3687,7 +3687,6 @@ gss_key_value_set_desc gss_name_t gtrgm_consistent_cache gzFile -hbaPort heap_page_items_state help_handler hlCheck From 4bd91912987d794c48dd4ba4c337906bd23759be Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 10:48:30 +0200 Subject: [PATCH 71/96] Change fmgr.h typedefs to use original names fmgr.h defined some types such as fmNodePtr which is just Node *, but it made its own types to avoid having to include various header files. With C11, we can now instead typedef the original names without fear of conflicts. Reviewed-by: Tom Lane Reviewed-by: Chao Li Discussion: https://www.postgresql.org/message-id/flat/10d32190-f31b-40a5-b177-11db55597355@eisentraut.org --- src/backend/utils/fmgr/fmgr.c | 5 ++--- src/include/fmgr.h | 30 +++++++++++++++--------------- src/include/utils/builtins.h | 2 +- src/tools/pgindent/typedefs.list | 4 ---- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 5543440a33e6c..b4c1e2c4b21b2 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1570,7 +1570,6 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod) * This is basically like InputFunctionCall, but the converted Datum is * returned into *result while the function result is true for success or * false for failure. Also, the caller may pass an ErrorSaveContext node. - * (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.) * * If escontext points to an ErrorSaveContext, any "soft" errors detected by * the input function will be reported by filling the escontext struct and @@ -1584,7 +1583,7 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod) bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod, - fmNodePtr escontext, + Node *escontext, Datum *result) { LOCAL_FCINFO(fcinfo, 3); @@ -1639,7 +1638,7 @@ InputFunctionCallSafe(FmgrInfo *flinfo, char *str, bool DirectInputFunctionCallSafe(PGFunction func, char *str, Oid typioparam, int32 typmod, - fmNodePtr escontext, + Node *escontext, Datum *result) { LOCAL_FCINFO(fcinfo, 3); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index c7236e4297242..74fe3ea05758a 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -19,14 +19,14 @@ #define FMGR_H /* We don't want to include primnodes.h here, so make some stub references */ -typedef struct Node *fmNodePtr; -typedef struct Aggref *fmAggrefPtr; +typedef struct Node Node; +typedef struct Aggref Aggref; /* Likewise, avoid including execnodes.h here */ -typedef void (*fmExprContextCallbackFunction) (Datum arg); +typedef void (*ExprContextCallbackFunction) (Datum arg); /* Likewise, avoid including stringinfo.h here */ -typedef struct StringInfoData *fmStringInfo; +typedef struct StringInfoData *StringInfo; /* @@ -63,7 +63,7 @@ typedef struct FmgrInfo unsigned char fn_stats; /* collect stats if track_functions > this */ void *fn_extra; /* extra space for use by handler */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */ - fmNodePtr fn_expr; /* expression parse tree for call, or NULL */ + Node *fn_expr; /* expression parse tree for call, or NULL */ } FmgrInfo; /* @@ -85,8 +85,8 @@ typedef struct FmgrInfo typedef struct FunctionCallInfoBaseData { FmgrInfo *flinfo; /* ptr to lookup info used for this call */ - fmNodePtr context; /* pass info about context of call */ - fmNodePtr resultinfo; /* pass or return extra info about result */ + Node *context; /* pass info about context of call */ + Node *resultinfo; /* pass or return extra info about result */ Oid fncollation; /* collation for function to use */ #define FIELDNO_FUNCTIONCALLINFODATA_ISNULL 4 bool isnull; /* function must set true if result is NULL */ @@ -742,19 +742,19 @@ extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod); extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod, - fmNodePtr escontext, + Node *escontext, Datum *result); extern bool DirectInputFunctionCallSafe(PGFunction func, char *str, Oid typioparam, int32 typmod, - fmNodePtr escontext, + Node *escontext, Datum *result); extern Datum OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod); extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val); extern char *OidOutputFunctionCall(Oid functionId, Datum val); -extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, fmStringInfo buf, +extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf, Oid typioparam, int32 typmod); -extern Datum OidReceiveFunctionCall(Oid functionId, fmStringInfo buf, +extern Datum OidReceiveFunctionCall(Oid functionId, StringInfo buf, Oid typioparam, int32 typmod); extern bytea *SendFunctionCall(FmgrInfo *flinfo, Datum val); extern bytea *OidSendFunctionCall(Oid functionId, Datum val); @@ -767,9 +767,9 @@ extern const Pg_finfo_record *fetch_finfo_record(void *filehandle, const char *f extern Oid fmgr_internal_function(const char *proname); extern Oid get_fn_expr_rettype(FmgrInfo *flinfo); extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); -extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum); +extern Oid get_call_expr_argtype(Node *expr, int argnum); extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum); -extern bool get_call_expr_arg_stable(fmNodePtr expr, int argnum); +extern bool get_call_expr_arg_stable(Node *expr, int argnum); extern bool get_fn_expr_variadic(FmgrInfo *flinfo); extern bytea *get_fn_opclass_options(FmgrInfo *flinfo); extern bool has_fn_opclass_options(FmgrInfo *flinfo); @@ -814,11 +814,11 @@ extern void RestoreLibraryState(char *start_address); extern int AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext); -extern fmAggrefPtr AggGetAggref(FunctionCallInfo fcinfo); +extern Aggref *AggGetAggref(FunctionCallInfo fcinfo); extern MemoryContext AggGetTempMemoryContext(FunctionCallInfo fcinfo); extern bool AggStateIsShared(FunctionCallInfo fcinfo); extern void AggRegisterCallback(FunctionCallInfo fcinfo, - fmExprContextCallbackFunction func, + ExprContextCallbackFunction func, Datum arg); /* diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 1c98c7d2255ce..ce6285a2c0376 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -80,7 +80,7 @@ extern PGDLLIMPORT bool quote_all_identifiers; extern const char *quote_identifier(const char *ident); extern char *quote_qualified_identifier(const char *qualifier, const char *ident); -extern void generate_operator_clause(fmStringInfo buf, +extern void generate_operator_clause(StringInfo buf, const char *leftop, Oid leftoptype, Oid opoid, const char *rightop, Oid rightoptype); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9db8e2424c038..e90af5b2ad364 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3635,10 +3635,6 @@ float8 float8KEY floating_decimal_32 floating_decimal_64 -fmAggrefPtr -fmExprContextCallbackFunction -fmNodePtr -fmStringInfo fmgr_hook_type foreign_glob_cxt foreign_loc_cxt From 9ec0b29976b6d73cc08880842caa0db286e46bd4 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 11:38:58 +0200 Subject: [PATCH 72/96] CREATE STATISTICS: improve misleading error message The previous change (commit f225473cbae) was still not on target, because it talked about relation kinds, which are not what is being checked here. Provide a more accurate message. Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CACJufxEZ48toGH0Em_6vdsT57Y3L8pLF=DZCQ_gCii6=C3MeXw@mail.gmail.com --- src/backend/tcop/utility.c | 3 +-- src/test/regress/expected/stats_ext.out | 21 +++++++-------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5f442bc3bd4e1..918db53dd5e7d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1874,8 +1874,7 @@ ProcessUtilitySlow(ParseState *pstate, if (!IsA(rel, RangeVar)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create statistics on the specified relation"), - errdetail("CREATE STATISTICS only supports tables, foreign tables and materialized views."))); + errmsg("CREATE STATISTICS only supports relation names in the FROM clause"))); /* * CREATE STATISTICS will influence future execution plans diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index a1f83b58b2398..fdc0aa130bdb8 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -56,29 +56,22 @@ CREATE STATISTICS tst (unrecognized) ON x, y FROM ext_stats_test; ERROR: unrecognized statistics kind "unrecognized" -- unsupported targets CREATE STATISTICS tst ON a FROM (VALUES (x)) AS foo; -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE STATISTICS tst ON a FROM foo NATURAL JOIN bar; -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE STATISTICS tst ON a FROM (SELECT * FROM ext_stats_test) AS foo; -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE STATISTICS tst ON a FROM ext_stats_test s TABLESAMPLE system (x); -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE STATISTICS tst ON a FROM XMLTABLE('foo' PASSING 'bar' COLUMNS a text); -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE STATISTICS tst ON a FROM JSON_TABLE(jsonb '123', '$' COLUMNS (item int)); -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause CREATE FUNCTION tftest(int) returns table(a int, b int) as $$ SELECT $1, $1+i FROM generate_series(1,5) g(i); $$ LANGUAGE sql IMMUTABLE STRICT; CREATE STATISTICS alt_stat2 ON a FROM tftest(1); -ERROR: cannot create statistics on the specified relation -DETAIL: CREATE STATISTICS only supports tables, foreign tables and materialized views. +ERROR: CREATE STATISTICS only supports relation names in the FROM clause DROP FUNCTION tftest; -- incorrect expressions CREATE STATISTICS tst ON (y) FROM ext_stats_test; -- single column reference From ce71993ae46930ddb1ff790807114384898b86a7 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 15 Sep 2025 16:27:50 +0200 Subject: [PATCH 73/96] Expand virtual generated columns in constraint expressions Virtual generated columns in constraint expressions need to be expanded because the optimizer matches these expressions to qual clauses. Failing to do so can cause us to miss opportunities for constraint exclusion. Author: Richard Guo Discussion: https://www.postgresql.org/message-id/flat/204804c0-798f-4c72-bd1f-36116024fda3%40eisentraut.org --- src/backend/optimizer/util/plancat.c | 9 ++++++++ .../regress/expected/generated_virtual.out | 23 +++++++++++++++++++ src/test/regress/sql/generated_virtual.sql | 15 ++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 572d626b2c4d2..f8641204a67ee 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -42,6 +42,7 @@ #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "partitioning/partdesc.h" +#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "statistics/statistics.h" #include "storage/bufmgr.h" @@ -1482,6 +1483,14 @@ get_relation_constraints(PlannerInfo *root, result = list_concat(result, rel->partition_qual); } + /* + * Expand virtual generated columns in the constraint expressions. + */ + if (result) + result = (List *) expand_generated_columns_in_expr((Node *) result, + relation, + varno); + table_close(relation, NoLock); return result; diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index aca6347babe96..d8645192351cf 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -1636,3 +1636,26 @@ select 1 from gtest32 t1 where exists (1 row) drop table gtest32; +-- Ensure that virtual generated columns in constraint expressions are expanded +create table gtest33 (a int, b int generated always as (a * 2) virtual not null, check (b > 10)); +set constraint_exclusion to on; +-- should get a dummy Result, not a seq scan +explain (costs off) +select * from gtest33 where b < 10; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +-- should get a dummy Result, not a seq scan +explain (costs off) +select * from gtest33 where b is null; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +reset constraint_exclusion; +drop table gtest33; diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index ba19bc4c701e0..adfe88d74ae98 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -868,3 +868,18 @@ select 1 from gtest32 t1 where exists (select 1 from gtest32 t2 where t1.a > t2.a and t2.b = 2); drop table gtest32; + +-- Ensure that virtual generated columns in constraint expressions are expanded +create table gtest33 (a int, b int generated always as (a * 2) virtual not null, check (b > 10)); +set constraint_exclusion to on; + +-- should get a dummy Result, not a seq scan +explain (costs off) +select * from gtest33 where b < 10; + +-- should get a dummy Result, not a seq scan +explain (costs off) +select * from gtest33 where b is null; + +reset constraint_exclusion; +drop table gtest33; From 7d9cd2df5ffc2939ac84581c9463b8afc4ca4c41 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Mon, 15 Sep 2025 16:56:49 -0400 Subject: [PATCH 74/96] Teach nbtree to avoid evaluating row compare keys. Add logic to _bt_set_startikey that determines whether row compare keys are guaranteed to be satisfied by every tuple on a page that is about to be read by _bt_readpage. This works in essentially the same way as the existing scalar inequality logic. Testing has shown that the new logic improves performance to about the same degree as the existing scalar inequality logic (compared to the unoptimized case). In other words, the new logic makes many row compare scans significantly faster. Note that the new row compare inequality logic is only effective when the same individual row member is the deciding subkey for all tuples on the page (obviously, all tuples have to satisfy the row compare, too). This is what makes the new row compare logic very similar to the existing logic for scalar inequalities. Note, in particular, that this makes it safe to ignore whether all row compare members are against either ASC or DESC index attributes (i.e. it doesn't matter if individual subkeys don't all use the same inequality strategy). Also stop refusing to set pstate.startikey to an offset beyond any nonrequired key (don't add logic that'll do that for an individual row compare subkey, either). We can fully rely on our firstchangingattnum tests instead. This will do the right thing when a page has a group of tuples with NULLs in a lower-order attribute that makes the tuples fail to satisfy a row compare key -- we won't incorrectly conclude that all tuples must satisfy the row compare, just because firsttup and lasttup happen to. Our firstchangingattnum test prevents that from happening. (Note that the original "avoid evaluating nbtree scan keys" mechanism added by commit e0b1ee17 couldn't support row compares due to issues with tuples that contain NULLs in a lower-order subkey's attribute. That original mechanism relied on requiredness markings, which the replacement _bt_set_startikey mechanism never really needed.) Follow up to commit 8a510275, which added the _bt_set_startikey optimization. _bt_set_startikey is now feature complete; there's no remaining kind of nbtree scan key that it still doesn't support. Author: Peter Geoghegan Reviewed-By: Chao Li Discussion: https://postgr.es/m/CAH2-WznL6Z3H_GTQze9d8T_Ls=cYbnd-_9f-Jo7aYgTGRUD58g@mail.gmail.com --- src/backend/access/nbtree/nbtutils.c | 166 +++++++++++++++++++++------ 1 file changed, 134 insertions(+), 32 deletions(-) diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index edfea2acaff66..41b4fbd1c37e8 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -62,6 +62,7 @@ static bool _bt_check_compare(IndexScanDesc scan, ScanDirection dir, IndexTuple tuple, int tupnatts, TupleDesc tupdesc, bool advancenonrequired, bool forcenonrequired, bool *continuescan, int *ikey); +static bool _bt_rowcompare_cmpresult(ScanKey subkey, int cmpresult); static bool _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts, TupleDesc tupdesc, ScanDirection dir, bool forcenonrequired, bool *continuescan); @@ -2438,15 +2439,103 @@ _bt_set_startikey(IndexScanDesc scan, BTReadPageState *pstate) * Determine if it's safe to set pstate.startikey to an offset to a * key that comes after this key, by examining this key */ - if (!(key->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD))) - { - /* Scan key isn't marked required (corner case) */ - break; /* unsafe */ - } if (key->sk_flags & SK_ROW_HEADER) { - /* RowCompare inequalities currently aren't supported */ - break; /* "unsafe" */ + /* RowCompare inequality (header key) */ + ScanKey subkey = (ScanKey) DatumGetPointer(key->sk_argument); + bool satisfied = false; + + for (;;) + { + int cmpresult; + bool firstsatisfies = false; + + if (subkey->sk_attno > firstchangingattnum) /* >, not >= */ + break; /* unsafe, preceding attr has multiple + * distinct values */ + + if (subkey->sk_flags & SK_ISNULL) + break; /* unsafe, unsatisfiable NULL subkey arg */ + + firstdatum = index_getattr(firsttup, subkey->sk_attno, + tupdesc, &firstnull); + lastdatum = index_getattr(lasttup, subkey->sk_attno, + tupdesc, &lastnull); + + if (firstnull || lastnull) + break; /* unsafe, NULL value won't satisfy subkey */ + + /* + * Compare the first tuple's datum for this row compare member + */ + cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func, + subkey->sk_collation, + firstdatum, + subkey->sk_argument)); + if (subkey->sk_flags & SK_BT_DESC) + INVERT_COMPARE_RESULT(cmpresult); + + if (cmpresult != 0 || (subkey->sk_flags & SK_ROW_END)) + { + firstsatisfies = _bt_rowcompare_cmpresult(subkey, + cmpresult); + if (!firstsatisfies) + { + /* Unsafe, firstdatum does not satisfy subkey */ + break; + } + } + + /* + * Compare the last tuple's datum for this row compare member + */ + cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func, + subkey->sk_collation, + lastdatum, + subkey->sk_argument)); + if (subkey->sk_flags & SK_BT_DESC) + INVERT_COMPARE_RESULT(cmpresult); + + if (cmpresult != 0 || (subkey->sk_flags & SK_ROW_END)) + { + if (!firstsatisfies) + { + /* + * It's only safe to set startikey beyond the row + * compare header key when both firsttup and lasttup + * satisfy the key as a whole based on the same + * deciding subkey/attribute. That can't happen now. + */ + break; /* unsafe */ + } + + satisfied = _bt_rowcompare_cmpresult(subkey, cmpresult); + break; /* safe iff 'satisfied' is true */ + } + + /* Move on to next row member/subkey */ + if (subkey->sk_flags & SK_ROW_END) + break; /* defensive */ + subkey++; + + /* + * We deliberately don't check if the next subkey has the same + * strategy as this iteration's subkey (which happens when + * subkeys for both ASC and DESC columns are used together), + * nor if any subkey is marked required. This is safe because + * in general all prior index attributes must have only one + * distinct value (across all of the tuples on the page) in + * order for us to even consider any subkey's attribute. + */ + } + + if (satisfied) + { + /* Safe, row compare satisfied by every tuple on page */ + continue; + } + + break; /* unsafe */ } if (key->sk_strategy != BTEqualStrategyNumber) { @@ -2914,6 +3003,42 @@ _bt_check_compare(IndexScanDesc scan, ScanDirection dir, return true; } +/* + * Call here when a row compare member returns a non-zero result, or with the + * result for the final ROW_END row compare member (no matter the cmpresult). + * + * cmpresult indicates the overall result of the row comparison (must already + * be commuted for DESC subkeys), and subkey is the deciding row member. + */ +static bool +_bt_rowcompare_cmpresult(ScanKey subkey, int cmpresult) +{ + bool satisfied; + + switch (subkey->sk_strategy) + { + case BTLessStrategyNumber: + satisfied = (cmpresult < 0); + break; + case BTLessEqualStrategyNumber: + satisfied = (cmpresult <= 0); + break; + case BTGreaterEqualStrategyNumber: + satisfied = (cmpresult >= 0); + break; + case BTGreaterStrategyNumber: + satisfied = (cmpresult > 0); + break; + default: + /* EQ and NE cases aren't allowed here */ + elog(ERROR, "unexpected strategy number %d", subkey->sk_strategy); + satisfied = false; /* keep compiler quiet */ + break; + } + + return satisfied; +} + /* * Test whether an indextuple satisfies a row-comparison scan condition. * @@ -3094,31 +3219,8 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts, subkey++; } - /* - * At this point cmpresult indicates the overall result of the row - * comparison, and subkey points to the deciding column (or the last - * column if the result is "="). - */ - switch (subkey->sk_strategy) - { - /* EQ and NE cases aren't allowed here */ - case BTLessStrategyNumber: - result = (cmpresult < 0); - break; - case BTLessEqualStrategyNumber: - result = (cmpresult <= 0); - break; - case BTGreaterEqualStrategyNumber: - result = (cmpresult >= 0); - break; - case BTGreaterStrategyNumber: - result = (cmpresult > 0); - break; - default: - elog(ERROR, "unexpected strategy number %d", subkey->sk_strategy); - result = 0; /* keep compiler quiet */ - break; - } + /* Final subkey/column determines if row compare is satisfied */ + result = _bt_rowcompare_cmpresult(subkey, cmpresult); if (!result && !forcenonrequired) { From b54e8dbfe3b17648770a60c1a1bf99479bb766ce Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 16 Sep 2025 10:35:12 +0900 Subject: [PATCH 75/96] pg_restore: Fix comment handling with --no-publications / --no-subscriptions. Previously, pg_restore did not skip comments on publications or subscriptions even when --no-publications or --no-subscriptions was specified. As a result, it could issue COMMENT commands for objects that were never created, causing those commands to fail. This commit fixes the issue by ensuring that comments on publications and subscriptions are also skipped when the corresponding options are used. Backpatch to all supported versions. Author: Jian He Co-authored-by: Fujii Masao Discussion: https://postgr.es/m/CACJufxHCt00pR9h51AVu6+yPD5J7JQn=7dQXxqacj0XyDhc-fA@mail.gmail.com Backpatch-through: 13 --- src/bin/pg_dump/pg_backup_archiver.c | 14 +++++++++ src/bin/pg_dump/t/002_pg_dump.pl | 44 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 058b5d659bacf..d64ce19b67317 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3048,6 +3048,20 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) strcmp(te->desc, "ROW SECURITY") == 0)) return 0; + /* + * If it's a comment on a publication or a subscription, maybe ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index e7a2d64f74130..1e8c6bbf22b63 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -650,6 +650,32 @@ 'postgres', ], }, + no_subscriptions => { + dump_cmd => [ + 'pg_dump', '--no-sync', + '--file' => "$tempdir/no_subscriptions.sql", + '--no-subscriptions', + '--statistics', + 'postgres', + ], + }, + no_subscriptions_restore => { + dump_cmd => [ + 'pg_dump', '--no-sync', + '--format' => 'custom', + '--file' => "$tempdir/no_subscriptions_restore.dump", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'custom', + '--file' => "$tempdir/no_subscriptions_restore.sql", + '--no-subscriptions', + '--statistics', + "$tempdir/no_subscriptions_restore.dump", + ], + }, no_table_access_method => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -873,6 +899,8 @@ no_policies => 1, no_privs => 1, no_statistics => 1, + no_subscriptions => 1, + no_subscriptions_restore => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -1846,6 +1874,10 @@ regexp => qr/^COMMENT ON SUBSCRIPTION sub1 IS 'comment on subscription';/m, like => { %full_runs, section_post_data => 1, }, + unlike => { + no_subscriptions => 1, + no_subscriptions_restore => 1, + }, }, 'COMMENT ON TEXT SEARCH CONFIGURATION dump_test.alt_ts_conf1' => { @@ -3363,6 +3395,10 @@ \QCREATE SUBSCRIPTION sub1 CONNECTION 'dbname=doesnotexist' PUBLICATION pub1 WITH (connect = false, slot_name = 'sub1', streaming = parallel);\E /xm, like => { %full_runs, section_post_data => 1, }, + unlike => { + no_subscriptions => 1, + no_subscriptions_restore => 1, + }, }, 'CREATE SUBSCRIPTION sub2' => { @@ -3374,6 +3410,10 @@ \QCREATE SUBSCRIPTION sub2 CONNECTION 'dbname=doesnotexist' PUBLICATION pub1 WITH (connect = false, slot_name = 'sub2', streaming = off, origin = none);\E /xm, like => { %full_runs, section_post_data => 1, }, + unlike => { + no_subscriptions => 1, + no_subscriptions_restore => 1, + }, }, 'CREATE SUBSCRIPTION sub3' => { @@ -3385,6 +3425,10 @@ \QCREATE SUBSCRIPTION sub3 CONNECTION 'dbname=doesnotexist' PUBLICATION pub1 WITH (connect = false, slot_name = 'sub3', streaming = on);\E /xm, like => { %full_runs, section_post_data => 1, }, + unlike => { + no_subscriptions => 1, + no_subscriptions_restore => 1, + }, }, From 66dabc06b1b0aaadfd5be640a2e3efbeea0fcaf5 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 16 Sep 2025 11:54:23 +0900 Subject: [PATCH 76/96] pg_restore: Fix comment handling with --no-policies. Previously, pg_restore did not skip comments on policies even when --no-policies was specified. As a result, it could issue COMMENT commands for policies that were never created, causing those commands to fail. This commit fixes the issue by ensuring that comments on policies are also skipped when --no-policies is used. Backpatch to v18, where --no-policies was added in pg_restore. Author: Jian He Co-authored-by: Fujii Masao Discussion: https://postgr.es/m/CACJufxHCt00pR9h51AVu6+yPD5J7JQn=7dQXxqacj0XyDhc-fA@mail.gmail.com Backpatch-through: 18 --- src/bin/pg_dump/pg_backup_archiver.c | 7 ++++- src/bin/pg_dump/t/002_pg_dump.pl | 46 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index d64ce19b67317..2f92fce44f681 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3049,10 +3049,15 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) return 0; /* - * If it's a comment on a publication or a subscription, maybe ignore it. + * If it's a comment on a policy, a publication, or a subscription, maybe + * ignore it. */ if (strcmp(te->desc, "COMMENT") == 0) { + if (ropt->no_policies && + strncmp(te->tag, "POLICY", strlen("POLICY")) == 0) + return 0; + if (ropt->no_publications && strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) return 0; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 1e8c6bbf22b63..fc5b9b52f8041 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -632,6 +632,23 @@ 'postgres', ], }, + no_policies_restore => { + dump_cmd => [ + 'pg_dump', '--no-sync', + '--format' => 'custom', + '--file' => "$tempdir/no_policies_restore.dump", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'custom', + '--file' => "$tempdir/no_policies_restore.sql", + '--no-policies', + '--statistics', + "$tempdir/no_policies_restore.dump", + ], + }, no_privs => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -897,6 +914,7 @@ no_large_objects => 1, no_owner => 1, no_policies => 1, + no_policies_restore => 1, no_privs => 1, no_statistics => 1, no_subscriptions => 1, @@ -1540,6 +1558,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -1858,6 +1877,27 @@ }, }, + 'COMMENT ON POLICY p1' => { + create_order => 55, + create_sql => 'COMMENT ON POLICY p1 ON dump_test.test_table + IS \'comment on policy\';', + regexp => + qr/^COMMENT ON POLICY p1 ON dump_test.test_table IS 'comment on policy';/m, + like => { + %full_runs, + %dump_test_schema_runs, + only_dump_test_table => 1, + section_post_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + exclude_test_table => 1, + no_policies => 1, + no_policies_restore => 1, + only_dump_measurement => 1, + }, + }, + 'COMMENT ON PUBLICATION pub1' => { create_order => 55, create_sql => 'COMMENT ON PUBLICATION pub1 @@ -3224,6 +3264,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -3246,6 +3287,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -3268,6 +3310,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -3290,6 +3333,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -3312,6 +3356,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, @@ -3334,6 +3379,7 @@ exclude_dump_test_schema => 1, exclude_test_table => 1, no_policies => 1, + no_policies_restore => 1, only_dump_measurement => 1, }, }, From bce18ef3c67be63ad2dd8d585260dcb813f5a525 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 16 Sep 2025 07:23:50 +0200 Subject: [PATCH 77/96] Fix incorrect const qualifier Commit 7202d72787d added in passing some const qualifiers, but the one on the postmaster_child_launch() startup_data argument was incorrect, because the function itself modifies the pointed-to data. This is hidden from the compiler because of casts. The qualifiers on the functions called by postmaster_child_launch() are still correct. --- src/backend/postmaster/launch_backend.c | 2 +- src/include/postmaster/postmaster.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index c5ef14e1eaae8..79708e5925937 100644 --- a/src/backend/postmaster/launch_backend.c +++ b/src/backend/postmaster/launch_backend.c @@ -229,7 +229,7 @@ PostmasterChildName(BackendType child_type) */ pid_t postmaster_child_launch(BackendType child_type, int child_slot, - const void *startup_data, size_t startup_data_len, + void *startup_data, size_t startup_data_len, ClientSocket *client_sock) { pid_t pid; diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 92497cd6a0fb5..753871071ac35 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -108,7 +108,7 @@ extern PGDLLIMPORT struct ClientSocket *MyClientSocket; /* prototypes for functions in launch_backend.c */ extern pid_t postmaster_child_launch(BackendType child_type, int child_slot, - const void *startup_data, + void *startup_data, size_t startup_data_len, struct ClientSocket *client_sock); const char *PostmasterChildName(BackendType child_type); From 0f42206531b3646f5bcda2bd35bb53fb0488eb00 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Tue, 16 Sep 2025 06:16:23 +0000 Subject: [PATCH 78/96] Fix intermittent BF failures in 035_conflicts. This commit addresses two sources of instability in the 035_conflicts test: Unstable VACUUM usage: The test previously relied on VACUUM to remove a deleted column, which can be unreliable due to concurrent background writer or checkpoint activity that may lock the page containing the deleted tuple. Since the test already verifies that replication_slot.xmin has advanced sufficiently to confirm the feature's correctness, so, the VACUUM step is removed to improve test stability. Timing-sensitive retention resumption check: The test includes a check to confirm that retention of conflict-relevant information resumes after setting max_retention_duration to 0. However, in some cases, the apply worker resumes retention immediately after the inactive slot is removed from synchronized_standby_slots, even before max_retention_duration is updated. This can happen if remote changes are applied in under 1ms, causing the test to timeout while waiting for a later log position. To ensure consistent behavior, this commit delays the removal of synchronized_standby_slots until after max_retention_duration is set to 0. Author: Zhijie Hou Reviewed-by: shveta malik Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/TY4PR01MB16907805DE4816E53C54708159414A@TY4PR01MB16907.jpnprd01.prod.outlook.com --- src/test/subscription/t/035_conflicts.pl | 32 ++++++++---------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index f2aee0f70df21..a526986c4e40a 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -342,15 +342,6 @@ ), "the xmin value of slot 'pg_conflict_detection' is updated on Node A"); -# Confirm that the dead tuple can be removed now -($cmdret, $stdout, $stderr) = $node_A->psql( - 'postgres', qq(VACUUM (verbose) public.tab;) -); - -ok( $stderr =~ - qr/1 removed, 1 remain, 0 are dead but not yet removable/, - 'the deleted column is removed'); - ############################################################################### # Ensure that the deleted tuple needed to detect an update_deleted conflict is # accessible via a sequential table scan. @@ -555,13 +546,6 @@ "the xmin value of slot 'pg_conflict_detection' is updated on subscriber" ); - # Confirm that the dead tuple can be removed now - ($cmdret, $stdout, $stderr) = - $node_A->psql('postgres', qq(VACUUM (verbose) public.tab;)); - - ok($stderr =~ qr/1 removed, 0 remain, 0 are dead but not yet removable/, - 'the deleted column is removed'); - # Get the commit timestamp for the publisher's update my $pub_ts = $node_B->safe_psql('postgres', "SELECT pg_xact_commit_timestamp(xmin) from tab where a=1;"); @@ -625,12 +609,6 @@ "SELECT subretentionactive FROM pg_subscription WHERE subname='$subname_AB';"); is($result, qq(f), 'retention is inactive'); -# Drop the physical slot and reset the synchronized_standby_slots setting -$node_B->safe_psql('postgres', - "SELECT * FROM pg_drop_replication_slot('blocker');"); -$node_B->adjust_conf('postgresql.conf', 'synchronized_standby_slots', "''"); -$node_B->reload; - ############################################################################### # Check that dead tuple retention resumes when the max_retention_duration is set # 0. @@ -642,6 +620,16 @@ $node_A->safe_psql('postgres', "ALTER SUBSCRIPTION $subname_AB SET (max_retention_duration = 0);"); +# Drop the physical slot and reset the synchronized_standby_slots setting. We +# change this after setting max_retention_duration to 0, ensuring consistent +# results in the test as the resumption becomes possible immediately after +# resetting synchronized_standby_slots, due to the smaller max_retention_duration +# value of 1ms. +$node_B->safe_psql('postgres', + "SELECT * FROM pg_drop_replication_slot('blocker');"); +$node_B->adjust_conf('postgresql.conf', 'synchronized_standby_slots', "''"); +$node_B->reload; + # Confirm that the retention resumes $node_A->wait_for_log( qr/logical replication worker for subscription "tap_sub_a_b" will resume retaining the information for detecting conflicts From 8e5b92928d2847f556f9fd75edf619935dc87010 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 16 Sep 2025 16:44:58 +0900 Subject: [PATCH 79/96] pg_dump: Fix dumping of security labels on subscriptions and event triggers. Previously, pg_dump incorrectly queried pg_seclabel to retrieve security labels for subscriptions, which are stored in pg_shseclabel as they are global objects. This could result in security labels for subscriptions not being dumped. This commit fixes the issue by updating pg_dump to query the pg_seclabels view, which aggregates entries from both pg_seclabel and pg_shseclabel. While querying pg_shseclabel directly for subscriptions was an alternative, using pg_seclabels is simpler and sufficient. In addition, pg_dump is updated to dump security labels on event triggers, which were previously omitted. Backpatch to all supported versions. Author: Jian He Co-authored-by: Fujii Masao Discussion: https://postgr.es/m/CACJufxHCt00pR9h51AVu6+yPD5J7JQn=7dQXxqacj0XyDhc-fA@mail.gmail.com Backpatch-through: 13 --- src/bin/pg_dump/pg_backup_archiver.c | 12 +++++++----- src/bin/pg_dump/pg_dump.c | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 2f92fce44f681..93814152a5fd0 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3325,12 +3325,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index b4c45ad803e94..802637fb24ea8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -16763,7 +16763,7 @@ collectSecLabels(Archive *fout) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -19473,6 +19473,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); From e56a601e067894002c8bbc19cef5c4ef584ba8ad Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 16 Sep 2025 08:59:12 +0200 Subject: [PATCH 80/96] Move pg_int64 back to postgres_ext.h Fix for commit 3c86223c998. That commit moved the typedef of pg_int64 from postgres_ext.h to libpq-fe.h, because the only remaining place where it might be used is libpq users, and since the type is obsolete, the intent was to limit its scope. The problem is that if someone builds an extension against an older (pre-PG18) server version and a new (PG18) libpq, they might get two typedefs, depending on include file order. This is not allowed under C99, so they might get warnings or errors, depending on the compiler and options. The underlying types might also be different (e.g., long int vs. long long int), which would also lead to errors. This scenario is plausible when using the standard Debian packaging, which provides only the newest libpq but per-major-version server packages. The fix is to undo that part of commit 3c86223c998. That way, the typedef is in the same header file across versions. At least, this is the safest fix doable before PostgreSQL 18 releases. Reviewed-by: Thomas Munro Discussion: https://www.postgresql.org/message-id/25144219-5142-4589-89f8-4e76948b32db%40eisentraut.org --- src/include/postgres_ext.h | 5 +++++ src/interfaces/libpq/libpq-fe.h | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h index bf45c50dcf318..6a8f25a9907e4 100644 --- a/src/include/postgres_ext.h +++ b/src/include/postgres_ext.h @@ -24,6 +24,8 @@ #ifndef POSTGRES_EXT_H #define POSTGRES_EXT_H +#include + /* * Object ID is a fundamental type in Postgres. */ @@ -42,6 +44,9 @@ typedef unsigned int Oid; /* the above needs */ +/* deprecated name for int64_t, formerly used in client API declarations */ +typedef int64_t pg_int64; + /* * Identifiers of error message fields. Kept here to keep common * between frontend and backend, and also to export them to libpq diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index af8004f952a56..0852584edae4b 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -234,9 +234,6 @@ typedef struct pgNotify struct pgNotify *next; /* list link */ } PGnotify; -/* deprecated name for int64_t */ -typedef int64_t pg_int64; - /* pg_usec_time_t is like time_t, but with microsecond resolution */ typedef int64_t pg_usec_time_t; From cfa6cd29271e67c43c1040e3420c1145fdcdceb7 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Tue, 16 Sep 2025 16:29:08 +0700 Subject: [PATCH 81/96] Generate GB18030 mappings from the Unicode Consortium's UCM file Previously we built the .map files for GB18030 (version 2000) from an XML file. The 2022 version for this encoding is only available as a Unicode Character Mapping (UCM) file, so as preparatory refactoring switch to this format as the source for building version 2000. As we do with most input files for the conversion mappings, download the file on demand. In order to generate the same mappings we have now, we must download from a previous upstream commit, rather than the head since the latter contains a correction not present in our current .map files. The XML file is still used by EUC_CN, so we cannot delete it from our repository. GB18030 is a superset of EUC_CN, so it may be possible to build EUC_CN from the same UCM file, but that is left for future work. Author: Chao Li Discussion: https://postgr.es/m/966d9fc.169.198741fe60b.Coremail.jiaoshuntian%40highgo.com --- src/backend/utils/mb/Unicode/Makefile | 5 +++- .../utils/mb/Unicode/UCS_to_GB18030.pl | 28 +++++++++++++------ .../utf8_and_gb18030/utf8_and_gb18030.c | 7 ++++- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/backend/utils/mb/Unicode/Makefile b/src/backend/utils/mb/Unicode/Makefile index ad789b31e54b5..27424b2a0010e 100644 --- a/src/backend/utils/mb/Unicode/Makefile +++ b/src/backend/utils/mb/Unicode/Makefile @@ -54,7 +54,7 @@ $(eval $(call map_rule,euc_cn,UCS_to_EUC_CN.pl,gb-18030-2000.xml)) $(eval $(call map_rule,euc_kr,UCS_to_EUC_KR.pl,KSX1001.TXT)) $(eval $(call map_rule,euc_tw,UCS_to_EUC_TW.pl,CNS11643.TXT)) $(eval $(call map_rule,sjis,UCS_to_SJIS.pl,CP932.TXT)) -$(eval $(call map_rule,gb18030,UCS_to_GB18030.pl,gb-18030-2000.xml)) +$(eval $(call map_rule,gb18030,UCS_to_GB18030.pl,gb-18030-2000.ucm)) $(eval $(call map_rule,big5,UCS_to_BIG5.pl,CP950.TXT BIG5.TXT CP950.TXT)) $(eval $(call map_rule,euc_jis_2004,UCS_to_EUC_JIS_2004.pl,euc-jis-2004-std.txt)) $(eval $(call map_rule,shift_jis_2004,UCS_to_SHIFT_JIS_2004.pl,sjis-0213-2004-std.txt)) @@ -78,6 +78,9 @@ euc-jis-2004-std.txt sjis-0213-2004-std.txt: gb-18030-2000.xml windows-949-2000.xml: $(DOWNLOAD) https://raw.githubusercontent.com/unicode-org/icu-data/master/charset/data/xml/$(@F) +gb-18030-2000.ucm: + $(DOWNLOAD) https://raw.githubusercontent.com/unicode-org/icu-data/d9d3a6ed27bb98a7106763e940258f0be8cd995b/charset/data/ucm/$(@F) + GB2312.TXT: $(DOWNLOAD) 'http://trac.greenstone.org/browser/trunk/gsdl/unicode/MAPPINGS/EASTASIA/GB/GB2312.TXT?rev=1842&format=txt' diff --git a/src/backend/utils/mb/Unicode/UCS_to_GB18030.pl b/src/backend/utils/mb/Unicode/UCS_to_GB18030.pl index ddcbd6ef0c478..084fdf66af1fd 100755 --- a/src/backend/utils/mb/Unicode/UCS_to_GB18030.pl +++ b/src/backend/utils/mb/Unicode/UCS_to_GB18030.pl @@ -5,13 +5,14 @@ # src/backend/utils/mb/Unicode/UCS_to_GB18030.pl # # Generate UTF-8 <--> GB18030 code conversion tables from -# "gb-18030-2000.xml", obtained from -# http://source.icu-project.org/repos/icu/data/trunk/charset/data/xml/ +# "gb-18030-2000.ucm", obtained from +# https://github.com/unicode-org/icu-data/tree/main/charset/data/ucm # # The lines we care about in the source file look like -# -# where the "u" field is the Unicode code point in hex, -# and the "b" field is the hex byte sequence for GB18030 +# \xYY[\xYY...] |n +# where XXXX is the Unicode code point in hex, +# and the \xYY... is the hex byte sequence for GB18030, +# and n is a flag indicating the type of mapping. use strict; use warnings FATAL => 'all'; @@ -22,7 +23,7 @@ # Read the input -my $in_file = "gb-18030-2000.xml"; +my $in_file = "gb-18030-2000.ucm"; open(my $in, '<', $in_file) || die("cannot open $in_file"); @@ -30,9 +31,18 @@ while (<$in>) { - next if (!m/\s+ + ((?:\\x[0-9A-Fa-f]{2})+)\s+ + \|(\d+)/x; + my ($u, $c, $flag) = ($1, $2, $3); + $c =~ s/\\x//g; + + # We only want round-trip mappings + next if ($flag ne '0'); + my $ucs = hex($u); my $code = hex($c); if ($code >= 0x80 && $ucs >= 0x0080) diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c b/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c index ffc9c58cd130b..a512df935777d 100644 --- a/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c +++ b/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c @@ -124,7 +124,12 @@ utf8word_to_unicode(uint32 c) /* * Perform mapping of GB18030 ranges to UTF8 * - * The ranges we need to convert are specified in gb-18030-2000.xml. + * General description, and the range we need to convert for U+10000 and up: + * https://htmlpreview.github.io/?https://github.com/unicode-org/icu-data/blob/main/charset/source/gb18030/gb18030.html + * + * Ranges up to U+FFFF: + * https://github.com/unicode-org/icu-data/blob/main/charset/source/gb18030/ranges.txt + * * All are ranges of 4-byte GB18030 codes. */ static uint32 From b63a822452152a7bd613fd4b28e0967e626e274e Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Tue, 16 Sep 2025 18:42:20 +0900 Subject: [PATCH 82/96] Treat JsonConstructorExpr as non-strict JsonConstructorExpr can produce non-NULL output with a NULL input, so it should be treated as a non-strict construct. Failing to do so can lead to incorrect query behavior. For example, in the reported case, when pulling up a subquery that is under an outer join, if the subquery's target list contains a JsonConstructorExpr that uses subquery variables and it is mistakenly treated as strict, it will be pulled up without being wrapped in a PlaceHolderVar. As a result, the expression will be evaluated at the wrong place and will not be forced to null when the outer join should do so. Back-patch to v16 where JsonConstructorExpr was introduced. Bug: #19046 Reported-by: Runyuan He Author: Tender Wang Co-authored-by: Richard Guo Discussion: https://postgr.es/m/19046-765b6602b0a8cfdf@postgresql.org Backpatch-through: 16 --- src/backend/optimizer/util/clauses.c | 2 ++ src/test/regress/expected/subselect.out | 28 +++++++++++++++++++++++++ src/test/regress/sql/subselect.sql | 17 +++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index ae0bd073ca917..f49bde7595b37 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1115,6 +1115,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; if (IsA(node, BooleanTest)) return true; + if (IsA(node, JsonConstructorExpr)) + return true; /* Check other function-containing nodes */ if (check_functions_in_node(node, contain_nonstrict_functions_checker, diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 7a1c216a0b1b7..307e5ca1f3d39 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1989,6 +1989,34 @@ fetch backward all in c1; commit; -- +-- Check that JsonConstructorExpr is treated as non-strict, and thus can be +-- wrapped in a PlaceHolderVar +-- +begin; +create temp table json_tab (a int); +insert into json_tab values (1); +explain (verbose, costs off) +select * from json_tab t1 left join (select json_array(1, a) from json_tab t2) s on false; + QUERY PLAN +--------------------------------------------------- + Nested Loop Left Join + Output: t1.a, (JSON_ARRAY(1, a RETURNING json)) + Join Filter: false + -> Seq Scan on pg_temp.json_tab t1 + Output: t1.a + -> Result + Output: JSON_ARRAY(1, a RETURNING json) + One-Time Filter: false +(8 rows) + +select * from json_tab t1 left join (select json_array(1, a) from json_tab t2) s on false; + a | json_array +---+------------ + 1 | +(1 row) + +rollback; +-- -- Verify that we correctly flatten cases involving a subquery output -- expression that doesn't need to be wrapped in a PlaceHolderVar -- diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index 8ccebbe51e06c..36a8a0aa1d587 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -996,6 +996,23 @@ fetch backward all in c1; commit; +-- +-- Check that JsonConstructorExpr is treated as non-strict, and thus can be +-- wrapped in a PlaceHolderVar +-- + +begin; + +create temp table json_tab (a int); +insert into json_tab values (1); + +explain (verbose, costs off) +select * from json_tab t1 left join (select json_array(1, a) from json_tab t2) s on false; + +select * from json_tab t1 left join (select json_array(1, a) from json_tab t2) s on false; + +rollback; + -- -- Verify that we correctly flatten cases involving a subquery output -- expression that doesn't need to be wrapped in a PlaceHolderVar From c7b0cb367d3c6b007122457ad5deb659fe8cc266 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 16 Sep 2025 10:09:49 -0400 Subject: [PATCH 83/96] Add regression expected-files for older OpenSSL in FIPS mode. In our previous discussions around making our regression tests pass in FIPS mode, we concluded that we didn't need to support the different error message wording observed with pre-3.0 OpenSSL. However there are still a few LTS distributions soldiering along with such versions, and now we have some in the buildfarm. So let's add the variant expected-files needed to make them happy. This commit only covers the core regression tests. Previous discussion suggested that we might need some adjustments in contrib as well, but it's not totally clear to me what those would be. Rather than work it out from first principles, I'll wait to see what the buildfarm shows. Back-patch to v17 which is the oldest branch that claims to support this case. Author: Tom Lane Discussion: https://postgr.es/m/443709.1757876535@sss.pgh.pa.us Backpatch-through: 17 --- src/test/regress/expected/md5_2.out | 35 +++++ src/test/regress/expected/password_2.out | 168 +++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/test/regress/expected/md5_2.out create mode 100644 src/test/regress/expected/password_2.out diff --git a/src/test/regress/expected/md5_2.out b/src/test/regress/expected/md5_2.out new file mode 100644 index 0000000000000..4eea7f2bfc360 --- /dev/null +++ b/src/test/regress/expected/md5_2.out @@ -0,0 +1,35 @@ +-- +-- MD5 test suite - from IETF RFC 1321 +-- (see: https://www.rfc-editor.org/rfc/rfc1321) +-- +-- (The md5() function will error in OpenSSL FIPS mode. By keeping +-- this test in a separate file, it is easier to manage variant +-- results.) +select md5('') = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('a') = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('abc') = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('message digest') = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('abcdefghijklmnopqrstuvwxyz') = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890') = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5(''::bytea) = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('a'::bytea) = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('abc'::bytea) = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('message digest'::bytea) = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('abcdefghijklmnopqrstuvwxyz'::bytea) = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'::bytea) = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890'::bytea) = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; +ERROR: could not compute MD5 hash: disabled for FIPS diff --git a/src/test/regress/expected/password_2.out b/src/test/regress/expected/password_2.out new file mode 100644 index 0000000000000..ea1fc7a083c6d --- /dev/null +++ b/src/test/regress/expected/password_2.out @@ -0,0 +1,168 @@ +-- +-- Tests for password types +-- +-- Tests for GUC password_encryption +SET password_encryption = 'novalue'; -- error +ERROR: invalid value for parameter "password_encryption": "novalue" +HINT: Available values: md5, scram-sha-256. +SET password_encryption = true; -- error +ERROR: invalid value for parameter "password_encryption": "true" +HINT: Available values: md5, scram-sha-256. +SET password_encryption = 'md5'; -- ok +SET password_encryption = 'scram-sha-256'; -- ok +-- consistency of password entries +SET password_encryption = 'md5'; +CREATE ROLE regress_passwd1; +ALTER ROLE regress_passwd1 PASSWORD 'role_pwd1'; +ERROR: password encryption failed: disabled for FIPS +CREATE ROLE regress_passwd2; +ALTER ROLE regress_passwd2 PASSWORD 'role_pwd2'; +ERROR: password encryption failed: disabled for FIPS +SET password_encryption = 'scram-sha-256'; +CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3'; +CREATE ROLE regress_passwd4 PASSWORD NULL; +-- check list of created entries +-- +-- The scram secret will look something like: +-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo= +-- +-- Since the salt is random, the exact value stored will be different on every test +-- run. Use a regular expression to mask the changing parts. +SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked + FROM pg_authid + WHERE rolname LIKE 'regress_passwd%' + ORDER BY rolname, rolpassword; + rolname | rolpassword_masked +-----------------+--------------------------------------------------- + regress_passwd1 | + regress_passwd2 | + regress_passwd3 | SCRAM-SHA-256$4096:$: + regress_passwd4 | +(4 rows) + +-- Rename a role +ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new; +-- md5 entry should have been removed +SELECT rolname, rolpassword + FROM pg_authid + WHERE rolname LIKE 'regress_passwd2_new' + ORDER BY rolname, rolpassword; + rolname | rolpassword +---------------------+------------- + regress_passwd2_new | +(1 row) + +ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2; +-- Change passwords with ALTER USER. With plaintext or already-encrypted +-- passwords. +SET password_encryption = 'md5'; +-- encrypt with MD5 +ALTER ROLE regress_passwd2 PASSWORD 'foo'; +ERROR: password encryption failed: disabled for FIPS +-- already encrypted, use as they are +ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70'; +WARNING: setting an MD5-encrypted password +DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. +HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. +ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; +SET password_encryption = 'scram-sha-256'; +-- create SCRAM secret +ALTER ROLE regress_passwd4 PASSWORD 'foo'; +-- already encrypted with MD5, use as it is +CREATE ROLE regress_passwd5 PASSWORD 'md5e73a4b11df52a6068f8b39f90be36023'; +WARNING: setting an MD5-encrypted password +DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. +HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. +-- This looks like a valid SCRAM-SHA-256 secret, but it is not +-- so it should be hashed with SCRAM-SHA-256. +CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234'; +-- These may look like valid MD5 secrets, but they are not, so they +-- should be hashed with SCRAM-SHA-256. +-- trailing garbage at the end +CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz'; +-- invalid length +CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz'; +-- Changing the SCRAM iteration count +SET scram_iterations = 1024; +CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount'; +SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked + FROM pg_authid + WHERE rolname LIKE 'regress_passwd%' + ORDER BY rolname, rolpassword; + rolname | rolpassword_masked +-----------------+--------------------------------------------------- + regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70 + regress_passwd2 | + regress_passwd3 | SCRAM-SHA-256$4096:$: + regress_passwd4 | SCRAM-SHA-256$4096:$: + regress_passwd5 | md5e73a4b11df52a6068f8b39f90be36023 + regress_passwd6 | SCRAM-SHA-256$4096:$: + regress_passwd7 | SCRAM-SHA-256$4096:$: + regress_passwd8 | SCRAM-SHA-256$4096:$: + regress_passwd9 | SCRAM-SHA-256$1024:$: +(9 rows) + +-- An empty password is not allowed, in any form +CREATE ROLE regress_passwd_empty PASSWORD ''; +NOTICE: empty string is not a valid password, clearing password +ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a'; +WARNING: setting an MD5-encrypted password +DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. +HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. +ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4='; +NOTICE: empty string is not a valid password, clearing password +SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty'; + rolpassword +------------- + +(1 row) + +-- Test with invalid stored and server keys. +-- +-- The first is valid, to act as a control. The others have too long +-- stored/server keys. They will be re-hashed. +CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; +CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; +CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='; +-- Check that the invalid secrets were re-hashed. A re-hashed secret +-- should not contain the original salt. +SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed + FROM pg_authid + WHERE rolname LIKE 'regress_passwd_sha_len%' + ORDER BY rolname; + rolname | is_rolpassword_rehashed +-------------------------+------------------------- + regress_passwd_sha_len0 | f + regress_passwd_sha_len1 | t + regress_passwd_sha_len2 | t +(3 rows) + +-- Test that valid hashes that are too long are rejected +CREATE ROLE regress_passwd10 PASSWORD 'SCRAM-SHA-256$000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:wNFxNSk1hAXBkgub8py3bg==$65zC6E+R0U7tiYTC9+Wtq4Thw6gUDj3eDCINij8TflU=:rC1I7tcVugrHEY2DT0iPjGyjM4aJxkMM9n8WBxtUtHU='; +ERROR: encrypted password is too long +DETAIL: Encrypted passwords must be no longer than 512 bytes. +ALTER ROLE regress_passwd9 PASSWORD 'SCRAM-SHA-256$000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:wNFxNSk1hAXBkgub8py3bg==$65zC6E+R0U7tiYTC9+Wtq4Thw6gUDj3eDCINij8TflU=:rC1I7tcVugrHEY2DT0iPjGyjM4aJxkMM9n8WBxtUtHU='; +ERROR: encrypted password is too long +DETAIL: Encrypted passwords must be no longer than 512 bytes. +DROP ROLE regress_passwd1; +DROP ROLE regress_passwd2; +DROP ROLE regress_passwd3; +DROP ROLE regress_passwd4; +DROP ROLE regress_passwd5; +DROP ROLE regress_passwd6; +DROP ROLE regress_passwd7; +DROP ROLE regress_passwd8; +DROP ROLE regress_passwd9; +DROP ROLE regress_passwd_empty; +DROP ROLE regress_passwd_sha_len0; +DROP ROLE regress_passwd_sha_len1; +DROP ROLE regress_passwd_sha_len2; +-- all entries should have been removed +SELECT rolname, rolpassword + FROM pg_authid + WHERE rolname LIKE 'regress_passwd%' + ORDER BY rolname, rolpassword; + rolname | rolpassword +---------+------------- +(0 rows) + From 83a56419457ec0eff2eddfed8eb3aba86bede9cc Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 16 Sep 2025 12:17:02 -0400 Subject: [PATCH 84/96] Provide more-specific error details/hints for function lookup failures. Up to now we've contented ourselves with a one-size-fits-all error hint when we fail to find any match to a function or procedure call. That was mostly okay in the beginning, but it was never great, and since the introduction of named arguments it's really not adequate. We at least ought to distinguish "function name doesn't exist" from "function name exists, but not with those argument names". And the rules for named-argument matching are arcane enough that some more detail seems warranted if we match the argument names but the call still doesn't work. This patch creates a framework for dealing with these problems: FuncnameGetCandidates and related code will now pass back a bitmask of flags showing how far the match succeeded. This allows a considerable amount of granularity in the reports. The set-bits-in-a-bitmask approach means that when there are multiple candidate functions, the report will reflect the match(es) that got the furthest, which seems correct. Also, we can avoid mentioning "maybe add casts" unless failure to match argument types is actually the issue. Extend the same return-a-bitmask approach to OpernameGetCandidates. The issues around argument names don't apply to operator syntax, but it still seems worth distinguishing between "there is no operator of that name" and "we couldn't match the argument types". While at it, adjust these messages and related ones to more strictly separate "detail" from "hint", following our message style guidelines' distinction between those. Reported-by: Dominique Devienne Author: Tom Lane Reviewed-by: Robert Haas Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us --- .../postgres_fdw/expected/postgres_fdw.out | 6 +- doc/src/sgml/sources.sgml | 7 +- doc/src/sgml/typeconv.sgml | 10 +- src/backend/catalog/namespace.c | 118 +++++++++++--- src/backend/catalog/pg_aggregate.c | 2 + src/backend/parser/parse_func.c | 148 ++++++++++++++++-- src/backend/parser/parse_oper.c | 69 ++++++-- src/backend/utils/adt/regproc.c | 19 ++- src/backend/utils/adt/ruleutils.c | 2 + src/include/catalog/namespace.h | 24 ++- src/include/parser/parse_func.h | 1 + src/pl/plperl/expected/plperl_elog.out | 2 +- src/pl/plperl/expected/plperl_elog_1.out | 2 +- src/pl/plpgsql/src/expected/plpgsql_call.out | 3 +- .../plpgsql/src/expected/plpgsql_record.out | 3 +- src/pl/plpython/expected/plpython_error.out | 2 +- .../traces/pipeline_abort.trace | 2 +- .../expected/test_extensions.out | 3 +- src/test/regress/expected/alter_table.out | 3 +- src/test/regress/expected/arrays.out | 3 +- src/test/regress/expected/create_cast.out | 6 +- .../regress/expected/create_function_sql.out | 3 +- src/test/regress/expected/create_operator.out | 19 +++ .../regress/expected/create_procedure.out | 5 +- src/test/regress/expected/create_view.out | 3 +- src/test/regress/expected/domain.out | 3 +- src/test/regress/expected/expressions.out | 3 +- src/test/regress/expected/geometry.out | 3 +- src/test/regress/expected/horology.out | 3 +- src/test/regress/expected/misc_functions.out | 4 +- src/test/regress/expected/multirangetypes.out | 15 +- src/test/regress/expected/plpgsql.out | 11 +- src/test/regress/expected/polymorphism.out | 143 +++++++++++++---- src/test/regress/expected/rangetypes.out | 9 +- src/test/regress/expected/rowtypes.out | 6 +- src/test/regress/expected/subselect.out | 3 +- src/test/regress/expected/temp.out | 2 +- src/test/regress/expected/text.out | 6 +- src/test/regress/expected/time.out | 3 +- src/test/regress/expected/timetz.out | 3 +- src/test/regress/expected/with.out | 3 +- src/test/regress/expected/xid.out | 12 +- src/test/regress/sql/create_operator.sql | 7 + src/test/regress/sql/polymorphism.sql | 20 +++ 44 files changed, 573 insertions(+), 151 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 18d727d77907a..cb74c87d1aaa3 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -4609,11 +4609,13 @@ SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; -- with that remote type SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR ERROR: operator does not exist: public.user_enum ~~ unknown -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down ERROR: operator does not exist: public.user_enum ~~ unknown -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; -- =================================================================== diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml index 261f19b3534b5..760f9b69d4778 100644 --- a/doc/src/sgml/sources.sgml +++ b/doc/src/sgml/sources.sgml @@ -153,11 +153,12 @@ ereport(ERROR, errmsg("function %s is not unique", func_signature_string(funcname, nargs, NIL, actual_arg_types)), - errhint("Unable to choose a best candidate function. " - "You might need to add explicit typecasts.")); + errdetail("Could not choose a best candidate function."), + errhint("You might need to add explicit type casts.")); This illustrates the use of format codes to embed run-time values into - a message text. Also, an optional hint message is provided. + a message text. Also, optional detail + and hint messages are provided. The auxiliary function calls can be written in any order, but conventionally errcode and errmsg appear first. diff --git a/doc/src/sgml/typeconv.sgml b/doc/src/sgml/typeconv.sgml index 2874874248668..1707bd884dcf1 100644 --- a/doc/src/sgml/typeconv.sgml +++ b/doc/src/sgml/typeconv.sgml @@ -465,9 +465,9 @@ try a similar case with ~, we get: SELECT ~ '20' AS "negation"; -ERROR: operator is not unique: ~ "unknown" -HINT: Could not choose a best candidate operator. You might need to add -explicit type casts. +ERROR: operator is not unique: ~ unknown +DETAIL: Could not choose a best candidate operator. +HINT: You might need to add explicit type casts. This happens because the system cannot decide which of the several possible ~ operators should be preferred. We can help @@ -901,8 +901,8 @@ the parser will try to convert that to text: SELECT substr(1234, 3); ERROR: function substr(integer, integer) does not exist -HINT: No function matches the given name and argument types. You might need -to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. This does not work because integer does not have an implicit cast diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 8bd4d6c3d4346..ed9aeee24bcb4 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -232,7 +232,7 @@ static void RemoveTempRelationsCallback(int code, Datum arg); static void InvalidationCallback(Datum arg, int cacheid, uint32 hashvalue); static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, bool include_out_arguments, int pronargs, - int **argnumbers); + int **argnumbers, int *fgc_flags); /* * Recomputing the namespace path can be costly when done frequently, such as @@ -1117,15 +1117,15 @@ TypeIsVisibleExt(Oid typid, bool *is_missing) /* * FuncnameGetCandidates - * Given a possibly-qualified function name and argument count, + * Given a possibly-qualified routine name, argument count, and arg names, * retrieve a list of the possible matches. * - * If nargs is -1, we return all functions matching the given name, + * If nargs is -1, we return all routines matching the given name, * regardless of argument count. (argnames must be NIL, and expand_variadic * and expand_defaults must be false, in this case.) * * If argnames isn't NIL, we are considering a named- or mixed-notation call, - * and only functions having all the listed argument names will be returned. + * and only routines having all the listed argument names will be returned. * (We assume that length(argnames) <= nargs and all the passed-in names are * distinct.) The returned structs will include an argnumbers array showing * the actual argument index for each logical argument position. @@ -1183,14 +1183,21 @@ TypeIsVisibleExt(Oid typid, bool *is_missing) * The caller might end up discarding such an entry anyway, but if it selects * such an entry it should react as though the call were ambiguous. * - * If missing_ok is true, an empty list (NULL) is returned if the name was - * schema-qualified with a schema that does not exist. Likewise if no - * candidate is found for other reasons. + * We return an empty list (NULL) if no suitable matches can be found. + * If the function name was schema-qualified with a schema that does not + * exist, then we return an empty list if missing_ok is true and otherwise + * throw an error. (missing_ok does not affect the behavior otherwise.) + * + * The output argument *fgc_flags is filled with a bitmask indicating how + * far we were able to match the supplied information. This is not of much + * interest if any candidates were found, but if not, it can help callers + * produce an on-point error message. */ FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, bool expand_defaults, - bool include_out_arguments, bool missing_ok) + bool include_out_arguments, bool missing_ok, + int *fgc_flags) { FuncCandidateList resultList = NULL; bool any_special = false; @@ -1203,15 +1210,20 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, /* check for caller error */ Assert(nargs >= 0 || !(expand_variadic | expand_defaults)); + /* initialize output fgc_flags to empty */ + *fgc_flags = 0; + /* deconstruct the name list */ DeconstructQualifiedName(names, &schemaname, &funcname); if (schemaname) { /* use exact schema given */ + *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */ namespaceId = LookupExplicitNamespace(schemaname, missing_ok); if (!OidIsValid(namespaceId)) return NULL; + *fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */ } else { @@ -1237,6 +1249,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, int *argnumbers = NULL; FuncCandidateList newResult; + *fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_proc */ + if (OidIsValid(namespaceId)) { /* Consider only procs in specified namespace */ @@ -1262,6 +1276,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, continue; /* proc is not in search path */ } + *fgc_flags |= FGC_NAME_VISIBLE; /* routine is in the right schema */ + /* * If we are asked to match to OUT arguments, then use the * proallargtypes array (which includes those); otherwise use @@ -1296,16 +1312,6 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, /* * Call uses named or mixed notation * - * Named or mixed notation can match a variadic function only if - * expand_variadic is off; otherwise there is no way to match the - * presumed-nameless parameters expanded from the variadic array. - */ - if (OidIsValid(procform->provariadic) && expand_variadic) - continue; - va_elem_type = InvalidOid; - variadic = false; - - /* * Check argument count. */ Assert(nargs >= 0); /* -1 not supported with argnames */ @@ -1324,11 +1330,32 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, if (pronargs != nargs && !use_defaults) continue; + /* We found a routine with a suitable number of arguments */ + *fgc_flags |= FGC_ARGCOUNT_MATCH; + /* Check for argument name match, generate positional mapping */ if (!MatchNamedCall(proctup, nargs, argnames, include_out_arguments, pronargs, - &argnumbers)) + &argnumbers, fgc_flags)) + continue; + + /* + * Named or mixed notation can match a variadic function only if + * expand_variadic is off; otherwise there is no way to match the + * presumed-nameless parameters expanded from the variadic array. + * However, we postpone the check until here because we want to + * perform argument name matching anyway (using the variadic array + * argument's name). This allows us to give an on-point error + * message if the user forgets to say VARIADIC in what would have + * been a valid call with it. + */ + if (OidIsValid(procform->provariadic) && expand_variadic) continue; + va_elem_type = InvalidOid; + variadic = false; + + /* We found a fully-valid call using argument names */ + *fgc_flags |= FGC_ARGNAMES_VALID; /* Named argument matching is always "special" */ any_special = true; @@ -1371,6 +1398,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, /* Ignore if it doesn't match requested argument count */ if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults) continue; + + /* We found a routine with a suitable number of arguments */ + *fgc_flags |= FGC_ARGCOUNT_MATCH; } /* @@ -1579,11 +1609,13 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames, * the mapping from call argument positions to actual function argument * numbers. Defaulted arguments are included in this map, at positions * after the last supplied argument. + * + * We also add flag bits to *fgc_flags reporting on how far the match got. */ static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, bool include_out_arguments, int pronargs, - int **argnumbers) + int **argnumbers, int *fgc_flags) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); int numposargs = nargs - list_length(argnames); @@ -1592,6 +1624,7 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, char **p_argnames; char *p_argmodes; bool arggiven[FUNC_MAX_ARGS]; + bool arg_filled_twice = false; bool isnull; int ap; /* call args position */ int pp; /* proargs position */ @@ -1645,9 +1678,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, continue; if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0) { - /* fail if argname matches a positional argument */ + /* note if argname matches a positional argument */ if (arggiven[pp]) - return false; + arg_filled_twice = true; arggiven[pp] = true; (*argnumbers)[ap] = pp; found = true; @@ -1664,6 +1697,16 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, Assert(ap == nargs); /* processed all actual parameters */ + /* If we get here, the function did match all the supplied argnames */ + *fgc_flags |= FGC_ARGNAMES_MATCH; + + /* ... however, some of them might have been placed wrong */ + if (arg_filled_twice) + return false; /* some argname matched a positional argument */ + + /* If we get here, the call doesn't have invalid mixed notation */ + *fgc_flags |= FGC_ARGNAMES_NONDUP; + /* Check for default arguments */ if (nargs < pronargs) { @@ -1682,6 +1725,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, Assert(ap == pronargs); /* processed all function parameters */ + /* If we get here, the call supplies all the required arguments */ + *fgc_flags |= FGC_ARGNAMES_ALL; + return true; } @@ -1745,11 +1791,13 @@ FunctionIsVisibleExt(Oid funcid, bool *is_missing) char *proname = NameStr(procform->proname); int nargs = procform->pronargs; FuncCandidateList clist; + int fgc_flags; visible = false; clist = FuncnameGetCandidates(list_make1(makeString(proname)), - nargs, NIL, false, false, false, false); + nargs, NIL, false, false, false, false, + &fgc_flags); for (; clist; clist = clist->next) { @@ -1882,9 +1930,20 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright) * * The returned items always have two args[] entries --- the first will be * InvalidOid for a prefix oprkind. nargs is always 2, too. + * + * We return an empty list (NULL) if no suitable matches can be found. If the + * operator name was schema-qualified with a schema that does not exist, then + * we return an empty list if missing_schema_ok is true and otherwise throw an + * error. (missing_schema_ok does not affect the behavior otherwise.) + * + * The output argument *fgc_flags is filled with a bitmask indicating how + * far we were able to match the supplied information. This is not of much + * interest if any candidates were found, but if not, it can help callers + * produce an on-point error message. */ FuncCandidateList -OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok) +OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok, + int *fgc_flags) { FuncCandidateList resultList = NULL; char *resultSpace = NULL; @@ -1895,15 +1954,20 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok) CatCList *catlist; int i; + /* initialize output fgc_flags to empty */ + *fgc_flags = 0; + /* deconstruct the name list */ DeconstructQualifiedName(names, &schemaname, &opername); if (schemaname) { /* use exact schema given */ + *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */ namespaceId = LookupExplicitNamespace(schemaname, missing_schema_ok); - if (missing_schema_ok && !OidIsValid(namespaceId)) + if (!OidIsValid(namespaceId)) return NULL; + *fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */ } else { @@ -1941,6 +2005,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok) if (oprkind && operform->oprkind != oprkind) continue; + *fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_operator */ + if (OidIsValid(namespaceId)) { /* Consider only opers in specified namespace */ @@ -2014,6 +2080,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok) } } + *fgc_flags |= FGC_NAME_VISIBLE; /* operator is in the right schema */ + /* * Okay to add it to result list */ diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index c62e8acd41375..a1cb5719a0c6d 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -836,6 +836,7 @@ lookup_agg_function(List *fnName, Oid vatype; Oid *true_oid_array; FuncDetailCode fdresult; + int fgc_flags; AclResult aclresult; int i; @@ -848,6 +849,7 @@ lookup_agg_function(List *fnName, */ fdresult = func_get_detail(fnName, NIL, NIL, nargs, input_types, false, false, false, + &fgc_flags, &fnOid, rettype, &retset, &nvargs, &vatype, &true_oid_array, NULL); diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 583bbbf232f04..c43020a769d1d 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -42,6 +42,8 @@ typedef enum FUNCLOOKUP_AMBIGUOUS, } FuncLookupError; +static int func_lookup_failure_details(int fgc_flags, List *argnames, + bool proc_call); static void unify_hypothetical_args(ParseState *pstate, List *fargs, int numAggregatedArgs, Oid *actual_arg_types, Oid *declared_arg_types); @@ -115,6 +117,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, int nvargs; Oid vatype; FuncDetailCode fdresult; + int fgc_flags; char aggkind = 0; ParseCallbackState pcbstate; @@ -266,6 +269,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, fdresult = func_get_detail(funcname, fargs, argnames, nargs, actual_arg_types, !func_variadic, true, proc_call, + &fgc_flags, &funcid, &rettype, &retset, &nvargs, &vatype, &declared_arg_types, &argdefaults); @@ -563,8 +567,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("procedure %s is not unique", func_signature_string(funcname, nargs, argnames, actual_arg_types)), - errhint("Could not choose a best candidate procedure. " - "You might need to add explicit type casts."), + errdetail("Could not choose a best candidate procedure."), + errhint("You might need to add explicit type casts."), parser_errposition(pstate, location))); else ereport(ERROR, @@ -572,8 +576,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("function %s is not unique", func_signature_string(funcname, nargs, argnames, actual_arg_types)), - errhint("Could not choose a best candidate function. " - "You might need to add explicit type casts."), + errdetail("Could not choose a best candidate function."), + errhint("You might need to add explicit type casts."), parser_errposition(pstate, location))); } else @@ -601,7 +605,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* * No function, and no column either. Since we're dealing with - * function notation, report "function does not exist". + * function notation, report "function/procedure does not exist". + * Depending on what was returned in fgc_flags, we can add some color + * to that with detail or hint messages. */ if (list_length(agg_order) > 1 && !agg_within_group) { @@ -611,8 +617,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("function %s does not exist", func_signature_string(funcname, nargs, argnames, actual_arg_types)), - errhint("No aggregate function matches the given name and argument types. " - "Perhaps you misplaced ORDER BY; ORDER BY must appear " + errdetail("No aggregate function matches the given name and argument types."), + errhint("Perhaps you misplaced ORDER BY; ORDER BY must appear " "after all regular arguments of the aggregate."), parser_errposition(pstate, location))); } @@ -622,8 +628,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("procedure %s does not exist", func_signature_string(funcname, nargs, argnames, actual_arg_types)), - errhint("No procedure matches the given name and argument types. " - "You might need to add explicit type casts."), + func_lookup_failure_details(fgc_flags, argnames, + proc_call), parser_errposition(pstate, location))); else ereport(ERROR, @@ -631,8 +637,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("function %s does not exist", func_signature_string(funcname, nargs, argnames, actual_arg_types)), - errhint("No function matches the given name and argument types. " - "You might need to add explicit type casts."), + func_lookup_failure_details(fgc_flags, argnames, + proc_call), parser_errposition(pstate, location))); } @@ -905,6 +911,104 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, return retval; } +/* + * Interpret the fgc_flags and issue a suitable detail or hint message. + * + * Helper function to reduce code duplication while throwing a + * function-not-found error. + */ +static int +func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call) +{ + /* + * If not FGC_NAME_VISIBLE, we shouldn't raise the question of whether the + * arguments are wrong. If the function name was not schema-qualified, + * it's helpful to distinguish between doesn't-exist-anywhere and + * not-in-search-path; but if it was, there's really nothing to add to the + * basic "function/procedure %s does not exist" message. + * + * Note: we passed missing_ok = false to FuncnameGetCandidates, so there's + * no need to consider FGC_SCHEMA_EXISTS here: we'd have already thrown an + * error if an explicitly-given schema doesn't exist. + */ + if (!(fgc_flags & FGC_NAME_VISIBLE)) + { + if (fgc_flags & FGC_SCHEMA_GIVEN) + return 0; /* schema-qualified name */ + else if (!(fgc_flags & FGC_NAME_EXISTS)) + { + if (proc_call) + return errdetail("There is no procedure of that name."); + else + return errdetail("There is no function of that name."); + } + else + { + if (proc_call) + return errdetail("A procedure of that name exists, but it is not in the search_path."); + else + return errdetail("A function of that name exists, but it is not in the search_path."); + } + } + + /* + * Next, complain if nothing had the right number of arguments. (This + * takes precedence over wrong-argnames cases because we won't even look + * at the argnames unless there's a workable number of arguments.) + */ + if (!(fgc_flags & FGC_ARGCOUNT_MATCH)) + { + if (proc_call) + return errdetail("No procedure of that name accepts the given number of arguments."); + else + return errdetail("No function of that name accepts the given number of arguments."); + } + + /* + * If there are argnames, and we failed to match them, again we should + * mention that and not bring up the argument types. + */ + if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_MATCH)) + { + if (proc_call) + return errdetail("No procedure of that name accepts the given argument names."); + else + return errdetail("No function of that name accepts the given argument names."); + } + + /* + * We could have matched all the given argnames and still not have had a + * valid call, either because of improper use of mixed notation, or + * because of missing arguments, or because the user misused VARIADIC. The + * rules about named-argument matching are finicky enough that it's worth + * trying to be specific about the problem. (The messages here are chosen + * with full knowledge of the steps that namespace.c uses while checking a + * potential match.) + */ + if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_NONDUP)) + return errdetail("In the closest available match, " + "an argument was specified both positionally and by name."); + + if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_ALL)) + return errdetail("In the closest available match, " + "not all required arguments were supplied."); + + if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_VALID)) + return errhint("This call would be correct if the variadic array were labeled VARIADIC and placed last."); + + if (fgc_flags & FGC_VARIADIC_FAIL) + return errhint("The VARIADIC parameter must be placed last, even when using argument names."); + + /* + * Otherwise, the problem must be incorrect argument types. + */ + if (proc_call) + (void) errdetail("No procedure of that name accepts the given argument types."); + else + (void) errdetail("No function of that name accepts the given argument types."); + return errhint("You might need to add explicit type casts."); +} + /* func_match_argtypes() * @@ -1372,9 +1476,14 @@ func_select_candidate(int nargs, * 1) check for possible interpretation as a type coercion request * 2) apply the ambiguous-function resolution rules * - * Return values *funcid through *true_typeids receive info about the function. - * If argdefaults isn't NULL, *argdefaults receives a list of any default - * argument expressions that need to be added to the given arguments. + * If there is no match at all, we return FUNCDETAIL_NOTFOUND, and *fgc_flags + * is filled with some flags that may be useful for issuing an on-point error + * message (see FuncnameGetCandidates). + * + * On success, return values *funcid through *true_typeids receive info about + * the function. If argdefaults isn't NULL, *argdefaults receives a list of + * any default argument expressions that need to be added to the given + * arguments. * * When processing a named- or mixed-notation call (ie, fargnames isn't NIL), * the returned true_typeids and argdefaults are ordered according to the @@ -1400,6 +1509,7 @@ func_get_detail(List *funcname, bool expand_variadic, bool expand_defaults, bool include_out_arguments, + int *fgc_flags, /* return value */ Oid *funcid, /* return value */ Oid *rettype, /* return value */ bool *retset, /* return value */ @@ -1424,7 +1534,8 @@ func_get_detail(List *funcname, /* Get list of possible candidates from namespace search */ raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames, expand_variadic, expand_defaults, - include_out_arguments, false); + include_out_arguments, false, + fgc_flags); /* * Quickly check if there is an exact match to the input datatypes (there @@ -1594,7 +1705,10 @@ func_get_detail(List *funcname, */ if (fargnames != NIL && !expand_variadic && nargs > 0 && best_candidate->argnumbers[nargs - 1] != nargs - 1) + { + *fgc_flags |= FGC_VARIADIC_FAIL; return FUNCDETAIL_NOTFOUND; + } *funcid = best_candidate->oid; *nvargs = best_candidate->nvargs; @@ -2053,6 +2167,7 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname, { Oid result = InvalidOid; FuncCandidateList clist; + int fgc_flags; /* NULL argtypes allowed for nullary functions only */ Assert(argtypes != NULL || nargs == 0); @@ -2062,7 +2177,8 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname, /* Get list of candidate objects */ clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false, - include_out_arguments, missing_ok); + include_out_arguments, missing_ok, + &fgc_flags); /* Scan list for a match to the arg types (if specified) and the objtype */ for (; clist != NULL; clist = clist->next) diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index 0c4337563cf35..7bd7a336fd6fd 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -72,7 +72,8 @@ static FuncDetailCode oper_select_candidate(int nargs, Oid *operOid); static void op_error(ParseState *pstate, List *op, Oid arg1, Oid arg2, - FuncDetailCode fdresult, int location); + FuncDetailCode fdresult, int fgc_flags, int location); +static int oper_lookup_failure_details(int fgc_flags, bool is_unary_op); static bool make_oper_cache_key(ParseState *pstate, OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId, int location); @@ -373,6 +374,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId, Oid operOid; OprCacheKey key; bool key_ok; + int fgc_flags = 0; FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; HeapTuple tup = NULL; @@ -404,7 +406,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId, FuncCandidateList clist; /* Get binary operators of given name */ - clist = OpernameGetCandidates(opname, 'b', false); + clist = OpernameGetCandidates(opname, 'b', false, &fgc_flags); /* No operators found? Then fail... */ if (clist != NULL) @@ -434,7 +436,8 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId, make_oper_cache_entry(&key, operOid); } else if (!noError) - op_error(pstate, opname, ltypeId, rtypeId, fdresult, location); + op_error(pstate, opname, ltypeId, rtypeId, + fdresult, fgc_flags, location); return (Operator) tup; } @@ -520,6 +523,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) Oid operOid; OprCacheKey key; bool key_ok; + int fgc_flags = 0; FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; HeapTuple tup = NULL; @@ -551,7 +555,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) FuncCandidateList clist; /* Get prefix operators of given name */ - clist = OpernameGetCandidates(op, 'l', false); + clist = OpernameGetCandidates(op, 'l', false, &fgc_flags); /* No operators found? Then fail... */ if (clist != NULL) @@ -585,7 +589,8 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) make_oper_cache_entry(&key, operOid); } else if (!noError) - op_error(pstate, op, InvalidOid, arg, fdresult, location); + op_error(pstate, op, InvalidOid, arg, + fdresult, fgc_flags, location); return (Operator) tup; } @@ -621,29 +626,67 @@ op_signature_string(List *op, Oid arg1, Oid arg2) static void op_error(ParseState *pstate, List *op, Oid arg1, Oid arg2, - FuncDetailCode fdresult, int location) + FuncDetailCode fdresult, int fgc_flags, int location) { if (fdresult == FUNCDETAIL_MULTIPLE) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_FUNCTION), errmsg("operator is not unique: %s", op_signature_string(op, arg1, arg2)), - errhint("Could not choose a best candidate operator. " - "You might need to add explicit type casts."), + errdetail("Could not choose a best candidate operator."), + errhint("You might need to add explicit type casts."), parser_errposition(pstate, location))); else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("operator does not exist: %s", op_signature_string(op, arg1, arg2)), - (!arg1 || !arg2) ? - errhint("No operator matches the given name and argument type. " - "You might need to add an explicit type cast.") : - errhint("No operator matches the given name and argument types. " - "You might need to add explicit type casts."), + oper_lookup_failure_details(fgc_flags, (!arg1 || !arg2)), parser_errposition(pstate, location))); } +/* + * Interpret the fgc_flags and issue a suitable detail or hint message. + */ +static int +oper_lookup_failure_details(int fgc_flags, bool is_unary_op) +{ + /* + * If not FGC_NAME_VISIBLE, we shouldn't raise the question of whether the + * arguments are wrong. If the operator name was not schema-qualified, + * it's helpful to distinguish between doesn't-exist-anywhere and + * not-in-search-path; but if it was, there's really nothing to add to the + * basic "operator does not exist" message. + * + * Note: we passed missing_ok = false to OpernameGetCandidates, so there's + * no need to consider FGC_SCHEMA_EXISTS here: we'd have already thrown an + * error if an explicitly-given schema doesn't exist. + */ + if (!(fgc_flags & FGC_NAME_VISIBLE)) + { + if (fgc_flags & FGC_SCHEMA_GIVEN) + return 0; /* schema-qualified name */ + else if (!(fgc_flags & FGC_NAME_EXISTS)) + return errdetail("There is no operator of that name."); + else + return errdetail("An operator of that name exists, but it is not in the search_path."); + } + + /* + * Otherwise, the problem must be incorrect argument type(s). + */ + if (is_unary_op) + { + (void) errdetail("No operator of that name accepts the given argument type."); + return errhint("You might need to add an explicit type cast."); + } + else + { + (void) errdetail("No operator of that name accepts the given argument types."); + return errhint("You might need to add explicit type casts."); + } +} + /* * make_op() * Operator expression construction. diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index af17a3421a02d..e5c2246f2c923 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -71,6 +71,7 @@ regprocin(PG_FUNCTION_ARGS) RegProcedure result; List *names; FuncCandidateList clist; + int fgc_flags; /* Handle "-" or numeric OID */ if (parseDashOrOid(pro_name_or_oid, &result, escontext)) @@ -93,7 +94,8 @@ regprocin(PG_FUNCTION_ARGS) if (names == NIL) PG_RETURN_NULL(); - clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true); + clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true, + &fgc_flags); if (clist == NULL) ereturn(escontext, (Datum) 0, @@ -164,13 +166,15 @@ regprocout(PG_FUNCTION_ARGS) { char *nspname; FuncCandidateList clist; + int fgc_flags; /* * Would this proc be found (uniquely!) by regprocin? If not, * qualify it. */ clist = FuncnameGetCandidates(list_make1(makeString(proname)), - -1, NIL, false, false, false, false); + -1, NIL, false, false, false, false, + &fgc_flags); if (clist != NULL && clist->next == NULL && clist->oid == proid) nspname = NULL; @@ -231,6 +235,7 @@ regprocedurein(PG_FUNCTION_ARGS) int nargs; Oid argtypes[FUNC_MAX_ARGS]; FuncCandidateList clist; + int fgc_flags; /* Handle "-" or numeric OID */ if (parseDashOrOid(pro_name_or_oid, &result, escontext)) @@ -251,8 +256,8 @@ regprocedurein(PG_FUNCTION_ARGS) escontext)) PG_RETURN_NULL(); - clist = FuncnameGetCandidates(names, nargs, NIL, false, false, - false, true); + clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false, true, + &fgc_flags); for (; clist; clist = clist->next) { @@ -483,6 +488,7 @@ regoperin(PG_FUNCTION_ARGS) Oid result; List *names; FuncCandidateList clist; + int fgc_flags; /* Handle "0" or numeric OID */ if (parseNumericOid(opr_name_or_oid, &result, escontext)) @@ -502,7 +508,7 @@ regoperin(PG_FUNCTION_ARGS) if (names == NIL) PG_RETURN_NULL(); - clist = OpernameGetCandidates(names, '\0', true); + clist = OpernameGetCandidates(names, '\0', true, &fgc_flags); if (clist == NULL) ereturn(escontext, (Datum) 0, @@ -572,13 +578,14 @@ regoperout(PG_FUNCTION_ARGS) else { FuncCandidateList clist; + int fgc_flags; /* * Would this oper be found (uniquely!) by regoperin? If not, * qualify it. */ clist = OpernameGetCandidates(list_make1(makeString(oprname)), - '\0', false); + '\0', false, &fgc_flags); if (clist != NULL && clist->next == NULL && clist->oid == oprid) result = pstrdup(oprname); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3d6e6bdbfd21b..0408a95941dcb 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -13265,6 +13265,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, bool use_variadic; char *nspname; FuncDetailCode p_result; + int fgc_flags; Oid p_funcid; Oid p_rettype; bool p_retset; @@ -13323,6 +13324,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, p_result = func_get_detail(list_make1(makeString(proname)), NIL, argnames, nargs, argtypes, !use_variadic, true, false, + &fgc_flags, &p_funcid, &p_rettype, &p_retset, &p_nvargs, &p_vatype, &p_true_typeids, NULL); diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 8c7ccc69a3c3d..f1423f28c3268 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -39,6 +39,24 @@ typedef struct _FuncCandidateList Oid args[FLEXIBLE_ARRAY_MEMBER]; /* arg types */ } *FuncCandidateList; +/* + * FuncnameGetCandidates also returns a bitmask containing these flags, + * which report on what it found or didn't find. They can help callers + * produce better error reports after a function lookup failure. + */ +#define FGC_SCHEMA_GIVEN 0x0001 /* Func name includes a schema */ +#define FGC_SCHEMA_EXISTS 0x0002 /* Found the explicitly-specified schema */ +#define FGC_NAME_EXISTS 0x0004 /* Found a routine by that name */ +#define FGC_NAME_VISIBLE 0x0008 /* Found a routine name/schema match */ +#define FGC_ARGCOUNT_MATCH 0x0010 /* Found a func with right # of args */ +/* These bits relate only to calls using named or mixed arguments: */ +#define FGC_ARGNAMES_MATCH 0x0020 /* Found a func matching all argnames */ +#define FGC_ARGNAMES_NONDUP 0x0040 /* argnames don't overlap positional args */ +#define FGC_ARGNAMES_ALL 0x0080 /* Found a func with no missing args */ +#define FGC_ARGNAMES_VALID 0x0100 /* Found a fully-valid use of argnames */ +/* These bits are actually filled by func_get_detail: */ +#define FGC_VARIADIC_FAIL 0x0200 /* Disallowed VARIADIC with named args */ + /* * Result of checkTempNamespaceStatus */ @@ -102,12 +120,14 @@ extern FuncCandidateList FuncnameGetCandidates(List *names, bool expand_variadic, bool expand_defaults, bool include_out_arguments, - bool missing_ok); + bool missing_ok, + int *fgc_flags); extern bool FunctionIsVisible(Oid funcid); extern Oid OpernameGetOprid(List *names, Oid oprleft, Oid oprright); extern FuncCandidateList OpernameGetCandidates(List *names, char oprkind, - bool missing_schema_ok); + bool missing_schema_ok, + int *fgc_flags); extern bool OperatorIsVisible(Oid oprid); extern Oid OpclassnameGetOpcid(Oid amid, const char *opcname); diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index a6f24b83d8436..218bb14c5d6b2 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -40,6 +40,7 @@ extern FuncDetailCode func_get_detail(List *funcname, int nargs, Oid *argtypes, bool expand_variadic, bool expand_defaults, bool include_out_arguments, + int *fgc_flags, Oid *funcid, Oid *rettype, bool *retset, int *nvargs, Oid *vatype, Oid **true_typeids, List **argdefaults); diff --git a/src/pl/plperl/expected/plperl_elog.out b/src/pl/plperl/expected/plperl_elog.out index a6d35cb79c4f9..6343962b81d33 100644 --- a/src/pl/plperl/expected/plperl_elog.out +++ b/src/pl/plperl/expected/plperl_elog.out @@ -41,7 +41,7 @@ select uses_global(); ERROR: function uses_global() does not exist LINE 1: select uses_global(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. SET plperl.use_strict = false; create or replace function uses_global() returns text language plperl as $$ diff --git a/src/pl/plperl/expected/plperl_elog_1.out b/src/pl/plperl/expected/plperl_elog_1.out index 85aa460ec4c2c..a85dd17b57999 100644 --- a/src/pl/plperl/expected/plperl_elog_1.out +++ b/src/pl/plperl/expected/plperl_elog_1.out @@ -41,7 +41,7 @@ select uses_global(); ERROR: function uses_global() does not exist LINE 1: select uses_global(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. SET plperl.use_strict = false; create or replace function uses_global() returns text language plperl as $$ diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out index ea7107dca0d90..3d0b117f236b8 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -440,7 +440,8 @@ $$; ERROR: procedure test_proc12(integer, integer, text[]) does not exist LINE 1: CALL test_proc12(_a, _b, _c) ^ -HINT: No procedure matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No procedure of that name accepts the given argument types. +HINT: You might need to add explicit type casts. QUERY: CALL test_proc12(_a, _b, _c) CONTEXT: PL/pgSQL function inline_code_block line 5 at CALL -- transition variable assignment diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out index e5de7143606ab..511f9e03c85d5 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_record.out +++ b/src/pl/plpgsql/src/expected/plpgsql_record.out @@ -466,7 +466,8 @@ select getf1(1); ERROR: function getf1(integer) does not exist LINE 1: select getf1(1); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select getf1(row(1,2)); getf1 ------- diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index fd9cd73be743a..96bbdfb95862c 100644 --- a/src/pl/plpython/expected/plpython_error.out +++ b/src/pl/plpython/expected/plpython_error.out @@ -63,7 +63,7 @@ SELECT exception_index_invalid_nested(); ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist LINE 1: SELECT test5('foo') ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. QUERY: SELECT test5('foo') CONTEXT: Traceback (most recent call last): PL/Python function "exception_index_invalid_nested", line 1, in diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace index cf6ccec6b9d19..3e5007d13b238 100644 --- a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace +++ b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace @@ -27,7 +27,7 @@ B 4 ParseComplete B 4 BindComplete B 4 NoData B 15 CommandComplete "INSERT 0 1" -B NN ErrorResponse S "ERROR" V "ERROR" C "42883" M "function no_such_function(integer) does not exist" H "No function matches the given name and argument types. You might need to add explicit type casts." P "8" F "SSSS" L "SSSS" R "SSSS" \x00 +B NN ErrorResponse S "ERROR" V "ERROR" C "42883" M "function no_such_function(integer) does not exist" D "There is no function of that name." P "8" F "SSSS" L "SSSS" R "SSSS" \x00 B 5 ReadyForQuery I B 4 ParseComplete B 4 BindComplete diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out index 72bae1bf254b5..fdae52d6ab2bf 100644 --- a/src/test/modules/test_extensions/expected/test_extensions.out +++ b/src/test/modules/test_extensions/expected/test_extensions.out @@ -333,7 +333,7 @@ SELECT ext_cor_func(); ERROR: function ext_cor_func() does not exist LINE 1: SELECT ext_cor_func(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. SELECT * FROM ext_cor_view; col ------------------------ @@ -649,7 +649,6 @@ SELECT dep_req3b(); -- fails ERROR: function public.dep_req2() does not exist LINE 1: SELECT public.dep_req2() || ' req3b' ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. QUERY: SELECT public.dep_req2() || ' req3b' CONTEXT: SQL function "dep_req3b" statement 1 DROP EXTENSION test_ext_req_schema3; diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index b33e06a0d3d51..a08f115b0e5a2 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2041,7 +2041,8 @@ alter table anothertab alter column atcol1 drop default; alter table anothertab alter column atcol1 type boolean using case when atcol1 % 2 = 0 then true else false end; -- fails ERROR: operator does not exist: boolean <= integer -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. alter table anothertab drop constraint anothertab_chk; alter table anothertab drop constraint anothertab_chk; -- fails ERROR: constraint "anothertab_chk" of relation "anothertab" does not exist diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index b815473f414b2..69ea2cf5ad803 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -2747,7 +2747,8 @@ SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); ERROR: function width_bucket(text, integer[]) does not exist LINE 1: SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. SELECT width_bucket(5, ARRAY[3, 4, NULL]); ERROR: thresholds array must not contain NULLs SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]); diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out index fd4871d94db72..0e69644bca2a0 100644 --- a/src/test/regress/expected/create_cast.out +++ b/src/test/regress/expected/create_cast.out @@ -28,14 +28,16 @@ SELECT casttestfunc('foo'::text); -- fails, as there's no cast ERROR: function casttestfunc(text) does not exist LINE 1: SELECT casttestfunc('foo'::text); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- Try binary coercion cast CREATE CAST (text AS casttesttype) WITHOUT FUNCTION; SELECT casttestfunc('foo'::text); -- doesn't work, as the cast is explicit ERROR: function casttestfunc(text) does not exist LINE 1: SELECT casttestfunc('foo'::text); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. SELECT casttestfunc('foo'::text::casttesttype); -- should work casttestfunc -------------- diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out index da112608d6619..73c6730d4591a 100644 --- a/src/test/regress/expected/create_function_sql.out +++ b/src/test/regress/expected/create_function_sql.out @@ -304,7 +304,8 @@ CREATE FUNCTION functest_S_xx(x date) RETURNS boolean ERROR: operator does not exist: date > integer LINE 3: RETURN x > 1; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- tricky parsing CREATE FUNCTION functest_S_15(x int) RETURNS boolean LANGUAGE SQL diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out index d776d9c18c3a7..5c7159665e221 100644 --- a/src/test/regress/expected/create_operator.out +++ b/src/test/regress/expected/create_operator.out @@ -24,6 +24,25 @@ SELECT @#@ 24; 620448401733239439360000 (1 row) +-- Test error cases +select @@##@@ 24; -- no such operator +ERROR: operator does not exist: @@##@@ integer +LINE 1: select @@##@@ 24; + ^ +DETAIL: There is no operator of that name. +set search_path = pg_catalog; +select @#@ 24; -- wrong schema +ERROR: operator does not exist: @#@ integer +LINE 1: select @#@ 24; + ^ +DETAIL: An operator of that name exists, but it is not in the search_path. +reset search_path; +select @#@ 24.0; -- wrong data type +ERROR: operator does not exist: @#@ numeric +LINE 1: select @#@ 24.0; + ^ +DETAIL: No operator of that name accepts the given argument type. +HINT: You might need to add an explicit type cast. -- Test comments COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; ERROR: operator does not exist: ###### integer diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out index 45b402e25e79c..f89042cf7987b 100644 --- a/src/test/regress/expected/create_procedure.out +++ b/src/test/regress/expected/create_procedure.out @@ -2,7 +2,7 @@ CALL nonexistent(); -- error ERROR: procedure nonexistent() does not exist LINE 1: CALL nonexistent(); ^ -HINT: No procedure matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no procedure of that name. CALL random(); -- error ERROR: random() is not a procedure LINE 1: CALL random(); @@ -299,7 +299,8 @@ CALL ptest9(1./0.); -- error ERROR: procedure ptest9(numeric) does not exist LINE 1: CALL ptest9(1./0.); ^ -HINT: No procedure matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No procedure of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- check named-parameter matching CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int) LANGUAGE SQL AS $$ SELECT b - c $$; diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index f551624afb3a3..49dd13c345cc5 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -1924,7 +1924,8 @@ select 'foo'::text = any((select array['abc','def','foo']::text[])); -- fail ERROR: operator does not exist: text = text[] LINE 1: select 'foo'::text = any((select array['abc','def','foo']::t... ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select 'foo'::text = any((select array['abc','def','foo']::text[])::text[]); ?column? ---------- diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index b5ea707df3103..62a48a523a2d8 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -415,7 +415,8 @@ select row(0,1)::dcomptype; -- fail ERROR: value for domain dcomptype violates check constraint "c1" alter type comptype alter attribute r type varchar; -- fail ERROR: operator does not exist: character varying > double precision -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. alter type comptype alter attribute r type bigint; alter type comptype drop attribute r; -- fail ERROR: cannot drop column r of composite type comptype because other objects depend on it diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out index 21c54fc1989c9..9a3c97b15a3ad 100644 --- a/src/test/regress/expected/expressions.out +++ b/src/test/regress/expected/expressions.out @@ -218,7 +218,8 @@ select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0)); ERROR: operator does not exist: point = box LINE 1: select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0)); ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- -- Tests for ScalarArrayOpExpr with a hashfn -- diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out index 8be694f46be1d..1d168b21cbca0 100644 --- a/src/test/regress/expected/geometry.out +++ b/src/test/regress/expected/geometry.out @@ -1777,7 +1777,8 @@ SELECT p.f1, l.s, l.s # p.f1 AS intersection ERROR: operator does not exist: lseg # point LINE 1: SELECT p.f1, l.s, l.s # p.f1 AS intersection ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- Length SELECT s, @-@ s FROM LSEG_TBL; s | ?column? diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index 5ae93d8e8a515..32cf62b674179 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -605,7 +605,8 @@ SELECT date '1991-02-03' - time with time zone '04:05:06 UTC' AS "Subtract Time ERROR: operator does not exist: date - time with time zone LINE 1: SELECT date '1991-02-03' - time with time zone '04:05:06 UTC... ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- -- timestamp, interval arithmetic -- diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index c3b2b9d860343..36164a99c8320 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -171,12 +171,12 @@ SELECT num_nonnulls(); ERROR: function num_nonnulls() does not exist LINE 1: SELECT num_nonnulls(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. SELECT num_nulls(); ERROR: function num_nulls() does not exist LINE 1: SELECT num_nulls(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. -- -- canonicalize_path() -- diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out index c6363ebeb24ca..63de4d09b1536 100644 --- a/src/test/regress/expected/multirangetypes.out +++ b/src/test/regress/expected/multirangetypes.out @@ -3096,7 +3096,8 @@ select multirange_of_text(textrange2('a','Z')); -- should fail ERROR: function multirange_of_text(textrange2) does not exist LINE 1: select multirange_of_text(textrange2('a','Z')); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select multirange_of_text(textrange1('a','Z')) @> 'b'::text; ERROR: range lower bound must be less than or equal to range upper bound select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e'))); @@ -3160,7 +3161,8 @@ select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20))); ERROR: function anyarray_anymultirange_func(integer[], nummultirange) does not exist LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyarray_anymultirange_func(anyarray, anymultirange); -- should fail create function bogus_func(anyelement) @@ -3199,7 +3201,8 @@ select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]); -- matc ERROR: function multirangetypes_sql(nummultirange, integer[]) does not exist LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange) returns anycompatible as 'select $1[1] + lower($2);' language sql; select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20))); @@ -3219,7 +3222,8 @@ select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange( ERROR: function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange); create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange) returns anycompatible as 'select lower($1) + lower($2);' language sql; @@ -3234,7 +3238,8 @@ select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange ERROR: function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange); -- should fail create function bogus_func(anycompatible) diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index d8ce39dba3c16..474be478ce8e3 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -1763,7 +1763,8 @@ select f1(point(3,4)); -- fail for lack of + operator ERROR: operator does not exist: point + integer LINE 1: x + 1 ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. QUERY: x + 1 CONTEXT: PL/pgSQL function f1(anyelement) line 3 at RETURN drop function f1(x anyelement); @@ -1848,7 +1849,8 @@ select f1(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit ERROR: function f1(int4range, integer, numeric) does not exist LINE 1: select f1(int4range(42, 49), 11, 4.5) as fail; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function f1(x anycompatiblerange, y anycompatible, z anycompatible); -- fail, can't infer type: create function f1(x anycompatible) returns anycompatiblerange as $$ @@ -1902,7 +1904,8 @@ select x, pg_typeof(x), y, pg_typeof(y) ERROR: function f1(integer, numeric[], integer, numeric) does not exist LINE 2: from f1(11, array[1, 2.2], 42, 34.5); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function f1(a anyelement, b anyarray, c anycompatible, d anycompatible); -- @@ -3072,7 +3075,7 @@ select shadowtest(1); ERROR: function shadowtest(integer) does not exist LINE 1: select shadowtest(1); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. reset plpgsql.extra_errors; reset plpgsql.extra_warnings; create or replace function shadowtest(f1 int) diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 94eedfe375eea..4f8447ec0d7f2 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -15,7 +15,8 @@ select polyf(point(3,4)); -- fail for lack of + operator ERROR: operator does not exist: point + integer LINE 2: select x + 1 ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. QUERY: select x + 1 @@ -95,7 +96,8 @@ select polyf(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit ERROR: function polyf(int4range, integer, numeric) does not exist LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible); create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$ select array[lower(x), upper(x), y, z] @@ -110,7 +112,8 @@ select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail; -- range type doe ERROR: function polyf(int4multirange, integer, numeric) does not exist LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible); -- fail, can't infer type: create function polyf(x anycompatible) returns anycompatiblerange as $$ @@ -176,7 +179,8 @@ select x, pg_typeof(x), y, pg_typeof(y) ERROR: function polyf(integer, numeric[], integer, numeric) does not exist LINE 2: from polyf(11, array[1, 2.2], 42, 34.5); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function polyf(a anyelement, b anyarray, c anycompatible, d anycompatible); create function polyf(anyrange) returns anymultirange @@ -990,7 +994,7 @@ select myleast(); -- fail ERROR: function myleast() does not exist LINE 1: select myleast(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. -- test with variadic call parameter select myleast(variadic array[1,2,3,4,-1]); myleast @@ -1060,17 +1064,20 @@ select formarray(1.1, array[1.2,55.5]); -- fail without variadic ERROR: function formarray(numeric, numeric[]) does not exist LINE 1: select formarray(1.1, array[1.2,55.5]); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select formarray(1, 'x'::text); -- fail, type mismatch ERROR: function formarray(integer, text) does not exist LINE 1: select formarray(1, 'x'::text); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select formarray(1, variadic array['x'::text]); -- fail, type mismatch ERROR: function formarray(integer, text[]) does not exist LINE 1: select formarray(1, variadic array['x'::text]); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function formarray(anyelement, variadic anyarray); -- test pg_typeof() function select pg_typeof(null); -- unknown @@ -1154,7 +1161,7 @@ select dfunc(10, 20, 30); -- fail ERROR: function dfunc(integer, integer, integer) does not exist LINE 1: select dfunc(10, 20, 30); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. drop function dfunc(); -- fail ERROR: function dfunc() does not exist drop function dfunc(int); -- fail @@ -1203,7 +1210,8 @@ select dfunc(); -- fail: which dfunc should be called? int or text ERROR: function dfunc() is not unique LINE 1: select dfunc(); ^ -HINT: Could not choose a best candidate function. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate function. +HINT: You might need to add explicit type casts. select dfunc('Hi'); -- ok dfunc ----------- @@ -1242,17 +1250,20 @@ select dfunc(); -- fail ERROR: function dfunc() is not unique LINE 1: select dfunc(); ^ -HINT: Could not choose a best candidate function. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate function. +HINT: You might need to add explicit type casts. select dfunc(1); -- fail ERROR: function dfunc(integer) is not unique LINE 1: select dfunc(1); ^ -HINT: Could not choose a best candidate function. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate function. +HINT: You might need to add explicit type casts. select dfunc(1, 2); -- fail ERROR: function dfunc(integer, integer) is not unique LINE 1: select dfunc(1, 2); ^ -HINT: Could not choose a best candidate function. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate function. +HINT: You might need to add explicit type casts. select dfunc(1, 2, 3); -- ok dfunc ------- @@ -1310,7 +1321,7 @@ select dfunc(); -- fail ERROR: function dfunc() does not exist LINE 1: select dfunc(); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. select dfunc(10); dfunc ------- @@ -1371,7 +1382,8 @@ select dfunc(1); -- fail ERROR: function dfunc(integer) is not unique LINE 1: select dfunc(1); ^ -HINT: Could not choose a best candidate function. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate function. +HINT: You might need to add explicit type casts. -- but this works since the ambiguous functions aren't preferred anyway select dfunc('Hi'); dfunc @@ -1417,7 +1429,7 @@ select * from dfunc(0); -- fail ERROR: function dfunc(integer) does not exist LINE 1: select * from dfunc(0); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given number of arguments. select * from dfunc(1,2); a | b | c | d ---+---+---+--- @@ -1448,18 +1460,72 @@ select * from dfunc(x := 10, b := 20, c := 30); -- fail, unknown param ERROR: function dfunc(x => integer, b => integer, c => integer) does not exist LINE 1: select * from dfunc(x := 10, b := 20, c := 30); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument names. select * from dfunc(10, 10, a := 20); -- fail, a overlaps positional parameter ERROR: function dfunc(integer, integer, a => integer) does not exist LINE 1: select * from dfunc(10, 10, a := 20); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: In the closest available match, an argument was specified both positionally and by name. select * from dfunc(1,c := 2,d := 3); -- fail, no value for b ERROR: function dfunc(integer, c => integer, d => integer) does not exist LINE 1: select * from dfunc(1,c := 2,d := 3); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: In the closest available match, not all required arguments were supplied. drop function dfunc(int, int, int, int); +create function xleast(x numeric, variadic arr numeric[]) + returns numeric as $$ + select least(x, min(arr[i])) from generate_subscripts(arr, 1) g(i); +$$ language sql; +select xleast(x => 1, variadic arr => array[2,3]); + xleast +-------- + 1 +(1 row) + +select xleast(1, variadic arr => array[2,3]); + xleast +-------- + 1 +(1 row) + +set search_path = pg_catalog; +select xleast(1, variadic arr => array[2,3]); -- wrong schema +ERROR: function xleast(integer, arr => integer[]) does not exist +LINE 1: select xleast(1, variadic arr => array[2,3]); + ^ +DETAIL: A function of that name exists, but it is not in the search_path. +reset search_path; +select xleast(foo => 1, variadic arr => array[2,3]); -- wrong argument name +ERROR: function xleast(foo => integer, arr => integer[]) does not exist +LINE 1: select xleast(foo => 1, variadic arr => array[2,3]); + ^ +DETAIL: No function of that name accepts the given argument names. +select xleast(x => 1, variadic array[2,3]); -- misuse of mixed notation +ERROR: positional argument cannot follow named argument +LINE 1: select xleast(x => 1, variadic array[2,3]); + ^ +select xleast(1, variadic x => array[2,3]); -- misuse of mixed notation +ERROR: function xleast(integer, x => integer[]) does not exist +LINE 1: select xleast(1, variadic x => array[2,3]); + ^ +DETAIL: In the closest available match, an argument was specified both positionally and by name. +select xleast(arr => array[1], variadic x => 3); -- wrong arg is VARIADIC +ERROR: function xleast(arr => integer[], x => integer) does not exist +LINE 1: select xleast(arr => array[1], variadic x => 3); + ^ +HINT: The VARIADIC parameter must be placed last, even when using argument names. +select xleast(arr => array[1], x => 3); -- failed to use VARIADIC +ERROR: function xleast(arr => integer[], x => integer) does not exist +LINE 1: select xleast(arr => array[1], x => 3); + ^ +HINT: This call would be correct if the variadic array were labeled VARIADIC and placed last. +select xleast(arr => 1, variadic x => array[2,3]); -- mixed-up args +ERROR: function xleast(arr => integer, x => integer[]) does not exist +LINE 1: select xleast(arr => 1, variadic x => array[2,3]); + ^ +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. +drop function xleast(x numeric, variadic arr numeric[]); -- test with different parameter types create function dfunc(a varchar, b numeric, c date = current_date) returns table (a varchar, b numeric, c date) as $$ @@ -1499,7 +1565,8 @@ select * from dfunc('Hello World', c := 20, b := '2009-07-25'::date); -- fail ERROR: function dfunc(unknown, c => integer, b => date) does not exist LINE 1: select * from dfunc('Hello World', c := 20, b := '2009-07-25... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function dfunc(varchar, numeric, date); -- test out parameters with named params create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric) @@ -1844,7 +1911,8 @@ select x, pg_typeof(x) from anyctest(11, point(1,2)) x; -- fail ERROR: function anyctest(integer, point) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, point(1,2)) x; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest('11', '12.3') x; -- defaults to text x | pg_typeof ------+----------- @@ -1872,7 +1940,8 @@ select x, pg_typeof(x) from anyctest(11, array[1,2]) x; -- fail ERROR: function anyctest(integer, integer[]) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, array[1,2]) x; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(anycompatible, anycompatible); create function anyctest(anycompatible, anycompatiblearray) returns anycompatiblearray as $$ @@ -1906,12 +1975,14 @@ select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) x; -- fail ERROR: function anyctest(integer, point[]) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) ... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest(11, 12) x; -- fail ERROR: function anyctest(integer, integer) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(anycompatible, anycompatiblearray); create function anyctest(anycompatible, anycompatiblerange) returns anycompatiblerange as $$ @@ -1933,12 +2004,14 @@ select x, pg_typeof(x) from anyctest(11, 12) x; -- fail ERROR: function anyctest(integer, integer) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x; -- fail ERROR: function anyctest(numeric, int4range) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest(11.2, '[4,7)') x; -- fail ERROR: could not determine polymorphic type anycompatiblerange because input has type unknown drop function anyctest(anycompatible, anycompatiblerange); @@ -1956,7 +2029,8 @@ select x, pg_typeof(x) from anyctest(int4range(11,12), numrange(4,7)) x; -- fail ERROR: function anyctest(int4range, numrange) does not exist LINE 1: select x, pg_typeof(x) from anyctest(int4range(11,12), numra... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(anycompatiblerange, anycompatiblerange); -- fail, can't infer result type: create function anyctest(anycompatible) @@ -1985,12 +2059,14 @@ select x, pg_typeof(x) from anyctest(11, 12) x; -- fail ERROR: function anyctest(integer, integer) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x; -- fail ERROR: function anyctest(numeric, int4multirange) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x; -- fail ERROR: could not determine polymorphic type anycompatiblemultirange because input has type unknown drop function anyctest(anycompatible, anycompatiblemultirange); @@ -2008,7 +2084,8 @@ select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(nu ERROR: function anyctest(int4multirange, nummultirange) does not exist LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(anycompatiblemultirange, anycompatiblemultirange); -- fail, can't infer result type: create function anyctest(anycompatible) @@ -2037,7 +2114,8 @@ select x, pg_typeof(x) from anyctest(array[11], array[1,2]) x; -- fail ERROR: function anyctest(integer[], integer[]) does not exist LINE 1: select x, pg_typeof(x) from anyctest(array[11], array[1,2]) ... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(anycompatiblenonarray, anycompatiblenonarray); create function anyctest(a anyelement, b anyarray, c anycompatible, d anycompatible) @@ -2066,7 +2144,8 @@ select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, 34.5) x; -- fail ERROR: function anyctest(integer, numeric[], integer, numeric) does not exist LINE 1: select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, ... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyctest(a anyelement, b anyarray, c anycompatible, d anycompatible); create function anyctest(variadic anycompatiblearray) diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out index a7cc220bf0d64..cdd95799cd5c3 100644 --- a/src/test/regress/expected/rangetypes.out +++ b/src/test/regress/expected/rangetypes.out @@ -1630,7 +1630,8 @@ select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); ERROR: function anyarray_anyrange_func(integer[], numrange) does not exist LINE 1: select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anyarray_anyrange_func(anyarray, anyrange); -- should fail create function bogus_func(anyelement) @@ -1669,7 +1670,8 @@ select rangetypes_sql(numrange(1,10), ARRAY[2,20]); -- match failure ERROR: function rangetypes_sql(numrange, integer[]) does not exist LINE 1: select rangetypes_sql(numrange(1,10), ARRAY[2,20]); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. create function anycompatiblearray_anycompatiblerange_func(a anycompatiblearray, r anycompatiblerange) returns anycompatible as 'select $1[1] + lower($2);' language sql; select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], int4range(10,20)); @@ -1689,7 +1691,8 @@ select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,2], int4range(10,20) ERROR: function anycompatiblearray_anycompatiblerange_func(numeric[], int4range) does not exist LINE 1: select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,... ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, anycompatiblerange); -- should fail create function bogus_func(anycompatible) diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 9168979a6206e..d84122881aff9 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -965,7 +965,8 @@ select text(fullname) from fullname; -- error ERROR: function text(fullname) does not exist LINE 1: select text(fullname) from fullname; ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select fullname.text from fullname; -- error ERROR: column fullname.text does not exist LINE 1: select fullname.text from fullname; @@ -987,7 +988,8 @@ select text(row('Jim', 'Beam')); -- error ERROR: function text(record) does not exist LINE 1: select text(row('Jim', 'Beam')); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select (row('Jim', 'Beam')).text; -- error ERROR: could not identify column "text" in record data type LINE 1: select (row('Jim', 'Beam')).text; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 307e5ca1f3d39..76ffb2b902750 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1175,7 +1175,8 @@ select * from int8_tbl where q1 in (select c1 from inner_text); ERROR: operator does not exist: bigint = text LINE 1: select * from int8_tbl where q1 in (select c1 from inner_tex... ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. begin; -- make an operator to allow it to succeed create function bogus_int8_text_eq(int8, text) returns boolean diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index 370361543b30c..a50c7ae88a9c8 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -229,7 +229,7 @@ select nonempty(''); ERROR: function nonempty(unknown) does not exist LINE 1: select nonempty(''); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: There is no function of that name. select pg_temp.nonempty(''); ERROR: value for domain nonempty violates check constraint "nonempty_check" -- other syntax matches rules for tables diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out index 4c65b238e764c..3f9982388baf3 100644 --- a/src/test/regress/expected/text.out +++ b/src/test/regress/expected/text.out @@ -27,7 +27,8 @@ select length(42); ERROR: function length(integer) does not exist LINE 1: select length(42); ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No function of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- But as a special exception for usability's sake, we still allow implicit -- casting to text in concatenations, so long as the other input is text or -- an unknown literal. So these work: @@ -48,7 +49,8 @@ select 3 || 4.0; ERROR: operator does not exist: integer || numeric LINE 1: select 3 || 4.0; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. /* * various string functions */ diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out index 4247fae9412b1..765adeb6e515c 100644 --- a/src/test/regress/expected/time.out +++ b/src/test/regress/expected/time.out @@ -157,7 +157,8 @@ SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL; ERROR: operator is not unique: time without time zone + time without time zone LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL; ^ -HINT: Could not choose a best candidate operator. You might need to add explicit type casts. +DETAIL: Could not choose a best candidate operator. +HINT: You might need to add explicit type casts. -- -- test EXTRACT -- diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out index cbab6cfe5d7f1..324b1a740e86b 100644 --- a/src/test/regress/expected/timetz.out +++ b/src/test/regress/expected/timetz.out @@ -174,7 +174,8 @@ SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TIMETZ_TBL; ERROR: operator does not exist: time with time zone + time with time zone LINE 1: SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TI... ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- -- test EXTRACT -- diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 26c8850514007..f015e9972761a 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -175,7 +175,8 @@ SELECT n, pg_typeof(n) FROM t; ERROR: operator does not exist: text + integer LINE 4: SELECT n+1 FROM t WHERE n < 10 ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- Deeply nested WITH caused a list-munging problem in v13 -- Detection of cross-references and self-references WITH RECURSIVE w1(c1) AS diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out index 835077e9d5785..1ce7826cf9047 100644 --- a/src/test/regress/expected/xid.out +++ b/src/test/regress/expected/xid.out @@ -110,22 +110,26 @@ select '1'::xid < '2'::xid; ERROR: operator does not exist: xid < xid LINE 1: select '1'::xid < '2'::xid; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select '1'::xid <= '2'::xid; ERROR: operator does not exist: xid <= xid LINE 1: select '1'::xid <= '2'::xid; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select '1'::xid > '2'::xid; ERROR: operator does not exist: xid > xid LINE 1: select '1'::xid > '2'::xid; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. select '1'::xid >= '2'::xid; ERROR: operator does not exist: xid >= xid LINE 1: select '1'::xid >= '2'::xid; ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- we want them for xid8 though select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8; ?column? | ?column? | ?column? diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql index a3096f17df0e2..19d30e1846076 100644 --- a/src/test/regress/sql/create_operator.sql +++ b/src/test/regress/sql/create_operator.sql @@ -22,6 +22,13 @@ CREATE OPERATOR #%# ( -- Test operator created above SELECT @#@ 24; +-- Test error cases +select @@##@@ 24; -- no such operator +set search_path = pg_catalog; +select @#@ 24; -- wrong schema +reset search_path; +select @#@ 24.0; -- wrong data type + -- Test comments COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix'; diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index fa57db6559c37..7337fd2d8f1ae 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -873,6 +873,26 @@ select * from dfunc(1,c := 2,d := 3); -- fail, no value for b drop function dfunc(int, int, int, int); +create function xleast(x numeric, variadic arr numeric[]) + returns numeric as $$ + select least(x, min(arr[i])) from generate_subscripts(arr, 1) g(i); +$$ language sql; + +select xleast(x => 1, variadic arr => array[2,3]); +select xleast(1, variadic arr => array[2,3]); + +set search_path = pg_catalog; +select xleast(1, variadic arr => array[2,3]); -- wrong schema +reset search_path; +select xleast(foo => 1, variadic arr => array[2,3]); -- wrong argument name +select xleast(x => 1, variadic array[2,3]); -- misuse of mixed notation +select xleast(1, variadic x => array[2,3]); -- misuse of mixed notation +select xleast(arr => array[1], variadic x => 3); -- wrong arg is VARIADIC +select xleast(arr => array[1], x => 3); -- failed to use VARIADIC +select xleast(arr => 1, variadic x => array[2,3]); -- mixed-up args + +drop function xleast(x numeric, variadic arr numeric[]); + -- test with different parameter types create function dfunc(a varchar, b numeric, c date = current_date) returns table (a varchar, b numeric, c date) as $$ From c044b50d199cf592eba2ac84f48cf0fcb5fb65f8 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Tue, 16 Sep 2025 09:40:44 -0700 Subject: [PATCH 85/96] Fix pg_dump COMMENT dependency for separate domain constraints. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The COMMENT should depend on the separately-dumped constraint, not the domain. Sufficient restore parallelism might fail the COMMENT command by issuing it before the constraint exists. Back-patch to v13, like commit 0858f0f96ebb891c8960994f023ed5a17b758a38. Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/20250913020233.fa.nmisch@google.com Backpatch-through: 13 --- src/bin/pg_dump/pg_dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 802637fb24ea8..9fc3671cb350c 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -18796,7 +18796,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } From 8abbbbae610cd62faad4f9ec18e7cfa908593796 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 16 Sep 2025 13:05:53 -0400 Subject: [PATCH 86/96] Revert "Avoid race condition between "GRANT role" and "DROP ROLE"". This reverts commit 98fc31d6499163a0a781aa6f13582a07f09cd7c6. That change allowed DROP OWNED BY to drop grants of the target role to other roles, arguing that nobody would need those privileges anymore. But that's not so: if you're not superuser, you still need admin privilege on the target role so you can drop it. It's not clear whether or how the dependency-based approach to solving the original problem can be adapted to keep these grants. Since v18 release is fast approaching, the sanest thing to do seems to be to revert this patch for now. The race-condition problem is low severity and not worth taking risks for. I didn't force a catversion bump in 98fc31d64, so I won't do so here either. Reported-by: Dipesh Dhameliya Author: Tom Lane Discussion: https://postgr.es/m/CABgZEgczOFicCJoqtrH9gbYMe_BV3Hq8zzCBRcMgmU6LRsihUA@mail.gmail.com Backpatch-through: 18 --- doc/src/sgml/ref/drop_owned.sgml | 2 +- src/backend/commands/user.c | 23 +++++------------- src/test/regress/expected/privileges.out | 30 ------------------------ src/test/regress/sql/privileges.sql | 15 ------------ 4 files changed, 7 insertions(+), 63 deletions(-) diff --git a/doc/src/sgml/ref/drop_owned.sgml b/doc/src/sgml/ref/drop_owned.sgml index 46e1c229ec0fb..efda01a39e88b 100644 --- a/doc/src/sgml/ref/drop_owned.sgml +++ b/doc/src/sgml/ref/drop_owned.sgml @@ -33,7 +33,7 @@ DROP OWNED BY { name | CURRENT_ROLE database that are owned by one of the specified roles. Any privileges granted to the given roles on objects in the current database or on shared objects (databases, tablespaces, configuration - parameters, or other roles) will also be revoked. + parameters) will also be revoked. diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 1e3d4ab0e20e7..6ae42ea565668 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -30,7 +30,6 @@ #include "commands/defrem.h" #include "commands/seclabel.h" #include "commands/user.h" -#include "lib/qunique.h" #include "libpq/crypt.h" #include "miscadmin.h" #include "storage/lmgr.h" @@ -490,7 +489,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) * Advance command counter so we can see new record; else tests in * AddRoleMems may fail. */ - CommandCounterIncrement(); + if (addroleto || adminmembers || rolemembers) + CommandCounterIncrement(); /* Default grant. */ InitGrantRoleOptions(&popt); @@ -1904,8 +1904,7 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid, else { Oid objectId; - Oid *newmembers = (Oid *) palloc(3 * sizeof(Oid)); - int nnewmembers; + Oid *newmembers = palloc(sizeof(Oid)); /* * The values for these options can be taken directly from 'popt'. @@ -1947,22 +1946,12 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid, new_record, new_record_nulls); CatalogTupleInsert(pg_authmem_rel, tuple); - /* - * Record dependencies on the roleid, member, and grantor, as if a - * pg_auth_members entry were an object ACL. - * updateAclDependencies() requires an input array that is - * palloc'd (it will free it), sorted, and de-duped. - */ - newmembers[0] = roleid; - newmembers[1] = memberid; - newmembers[2] = grantorId; - qsort(newmembers, 3, sizeof(Oid), oid_cmp); - nnewmembers = qunique(newmembers, 3, sizeof(Oid), oid_cmp); - + /* updateAclDependencies wants to pfree array inputs */ + newmembers[0] = grantorId; updateAclDependencies(AuthMemRelationId, objectId, 0, InvalidOid, 0, NULL, - nnewmembers, newmembers); + 1, newmembers); } /* CCI after each change, in case there are duplicates in list */ diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 6dcc95ede502c..daafaa94fdec0 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -113,36 +113,6 @@ CREATE USER regress_priv_user2; CREATE USER regress_priv_user3; CREATE USER regress_priv_user4; CREATE USER regress_priv_user5; --- DROP OWNED should also act on granted and granted-to roles -GRANT regress_priv_user1 TO regress_priv_user2; -GRANT regress_priv_user2 TO regress_priv_user3; -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; - roleid | member ---------------------+-------------------- - regress_priv_user1 | regress_priv_user2 - regress_priv_user2 | regress_priv_user3 -(2 rows) - -REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; -- no effect -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; - roleid | member ---------------------+-------------------- - regress_priv_user1 | regress_priv_user2 - regress_priv_user2 | regress_priv_user3 -(2 rows) - -DROP OWNED BY regress_priv_user2; -- removes both grants -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; - roleid | member ---------+-------- -(0 rows) - GRANT pg_read_all_data TO regress_priv_user6; GRANT pg_write_all_data TO regress_priv_user7; GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index fe409654c0e98..96eff1104d258 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -90,21 +90,6 @@ CREATE USER regress_priv_user3; CREATE USER regress_priv_user4; CREATE USER regress_priv_user5; --- DROP OWNED should also act on granted and granted-to roles -GRANT regress_priv_user1 TO regress_priv_user2; -GRANT regress_priv_user2 TO regress_priv_user3; -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; -REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; -- no effect -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; -DROP OWNED BY regress_priv_user2; -- removes both grants -SELECT roleid::regrole, member::regrole FROM pg_auth_members - WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole) - ORDER BY roleid::regrole::text; - GRANT pg_read_all_data TO regress_priv_user6; GRANT pg_write_all_data TO regress_priv_user7; GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; From e633fa6351bd7e7bc10a5e74168eed4c15992851 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 16 Sep 2025 14:36:51 -0400 Subject: [PATCH 87/96] Add regression expected-files for older OpenSSL in FIPS mode. Cover contrib/pgcrypto, per buildfarm. Author: Tom Lane Discussion: https://postgr.es/m/443709.1757876535@sss.pgh.pa.us Backpatch-through: 17 --- contrib/pgcrypto/expected/hmac-md5_2.out | 44 ++++++++++++++++++++++++ contrib/pgcrypto/expected/md5_2.out | 17 +++++++++ 2 files changed, 61 insertions(+) create mode 100644 contrib/pgcrypto/expected/hmac-md5_2.out create mode 100644 contrib/pgcrypto/expected/md5_2.out diff --git a/contrib/pgcrypto/expected/hmac-md5_2.out b/contrib/pgcrypto/expected/hmac-md5_2.out new file mode 100644 index 0000000000000..08cdf95d53245 --- /dev/null +++ b/contrib/pgcrypto/expected/hmac-md5_2.out @@ -0,0 +1,44 @@ +-- +-- HMAC-MD5 +-- +SELECT hmac( +'Hi There', +'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 2 +SELECT hmac( +'Jefe', +'what do ya want for nothing?', +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 3 +SELECT hmac( +'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea, +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 4 +SELECT hmac( +'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea, +'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 5 +SELECT hmac( +'Test With Truncation', +'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 6 +SELECT hmac( +'Test Using Larger Than Block-Size Key - Hash Key First', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm +-- 7 +SELECT hmac( +'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); +ERROR: Cannot use "md5": No such hash algorithm diff --git a/contrib/pgcrypto/expected/md5_2.out b/contrib/pgcrypto/expected/md5_2.out new file mode 100644 index 0000000000000..51bdaa86f32b4 --- /dev/null +++ b/contrib/pgcrypto/expected/md5_2.out @@ -0,0 +1,17 @@ +-- +-- MD5 message digest +-- +SELECT digest('', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('a', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('abc', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('message digest', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('abcdefghijklmnopqrstuvwxyz', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'md5'); +ERROR: Cannot use "md5": No such hash algorithm From dee21ea6d6178f1acd5044fd94e3dc4542e2656b Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 17 Sep 2025 11:48:55 +1200 Subject: [PATCH 88/96] Add missing EPQ recheck for TID Scan The EvalPlanQual recheck for TID Scan wasn't rechecking the TID qual still passed after following update chains. This could result in tuples being updated or deleted by plans using TID Scans where the ctid of the new (updated) tuple no longer matches the clause of the scan. This isn't desired behavior, and isn't consistent with what would happen if the chosen plan had used an Index or Seq Scan, and that could lead to hard to predict behavior for scans that contain TID quals and other quals as the planner has freedom to choose TID or some other scan method for such queries, and the chosen plan could change at any moment. Here we fix this by properly implementing the recheck function for TID Scans. Backpatch to 13, oldest supported version Reported-by: Sophie Alpert Author: Sophie Alpert Author: David Rowley Reviewed-by: David Rowley Reviewed-by: Chao Li Discussion: https://postgr.es/m/4a6268ff-3340-453a-9bf5-c98d51a6f729@app.fastmail.com Backpatch-through: 13 --- src/backend/executor/nodeTidscan.c | 19 +++-- .../isolation/expected/eval-plan-qual.out | 71 +++++++++++++++++++ src/test/isolation/specs/eval-plan-qual.spec | 11 +++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 5e56e29a15fc4..d50c6600358c5 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -402,12 +402,23 @@ TidNext(TidScanState *node) static bool TidRecheck(TidScanState *node, TupleTableSlot *slot) { + ItemPointer match; + + /* WHERE CURRENT OF always intends to resolve to the latest tuple */ + if (node->tss_isCurrentOf) + return true; + + if (node->tss_TidList == NULL) + TidListEval(node); + /* - * XXX shouldn't we check here to make sure tuple matches TID list? In - * runtime-key case this is not certain, is it? However, in the WHERE - * CURRENT OF case it might not match anyway ... + * Binary search the TidList to see if this ctid is mentioned and return + * true if it is. */ - return true; + match = (ItemPointer) bsearch(&slot->tts_tid, node->tss_TidList, + node->tss_NumTids, sizeof(ItemPointerData), + itemptr_comparator); + return match != NULL; } diff --git a/src/test/isolation/expected/eval-plan-qual.out b/src/test/isolation/expected/eval-plan-qual.out index 032d4208d51f3..2cba44f2bde3f 100644 --- a/src/test/isolation/expected/eval-plan-qual.out +++ b/src/test/isolation/expected/eval-plan-qual.out @@ -1218,6 +1218,77 @@ subid|id (1 row) +starting permutation: tid1 tid2 c1 c2 read +step tid1: UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; +accountid|balance +---------+------- +checking | 700 +(1 row) + +step tid2: UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' RETURNING accountid, balance; +step c1: COMMIT; +step tid2: <... completed> +accountid|balance +---------+------- +(0 rows) + +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid|balance|balance2 +---------+-------+-------- +checking | 700| 1400 +savings | 600| 1200 +(2 rows) + + +starting permutation: tid1 tidsucceed2 c1 c2 read +step tid1: UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; +accountid|balance +---------+------- +checking | 700 +(1 row) + +step tidsucceed2: UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' OR ctid = '(0,3)' RETURNING accountid, balance; +step c1: COMMIT; +step tidsucceed2: <... completed> +accountid|balance +---------+------- +checking | 900 +(1 row) + +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid|balance|balance2 +---------+-------+-------- +checking | 900| 1800 +savings | 600| 1200 +(2 rows) + + +starting permutation: tid1 tid2 r1 c2 read +step tid1: UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; +accountid|balance +---------+------- +checking | 700 +(1 row) + +step tid2: UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' RETURNING accountid, balance; +step r1: ROLLBACK; +step tid2: <... completed> +accountid|balance +---------+------- +checking | 800 +(1 row) + +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid|balance|balance2 +---------+-------+-------- +checking | 800| 1600 +savings | 600| 1200 +(2 rows) + + starting permutation: simplepartupdate conditionalpartupdate c1 c2 read_part step simplepartupdate: update parttbl set b = b + 10; diff --git a/src/test/isolation/specs/eval-plan-qual.spec b/src/test/isolation/specs/eval-plan-qual.spec index 07307e623e473..b8983ea215f18 100644 --- a/src/test/isolation/specs/eval-plan-qual.spec +++ b/src/test/isolation/specs/eval-plan-qual.spec @@ -99,6 +99,9 @@ step upsert1 { WHERE NOT EXISTS (SELECT 1 FROM upsert); } +# Tests for Tid Scan +step tid1 { UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; } + # tests with table p check inheritance cases: # readp1/writep1/readp2 tests a bug where nodeLockRows did the wrong thing # when the first updated tuple was in a non-first child table. @@ -241,6 +244,10 @@ step updateforcip3 { step wrtwcte { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; } step wrjt { UPDATE jointest SET data = 42 WHERE id = 7; } +step tid2 { UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' RETURNING accountid, balance; } +# here, recheck succeeds; (0,3) is the id that step tid1 will assign +step tidsucceed2 { UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' OR ctid = '(0,3)' RETURNING accountid, balance; } + step conditionalpartupdate { update parttbl set c = -c where b < 10; } @@ -392,6 +399,10 @@ permutation wrtwcte readwcte c1 c2 permutation wrjt selectjoinforupdate c2 c1 permutation wrjt selectresultforupdate c2 c1 permutation wrtwcte multireadwcte c1 c2 +permutation tid1 tid2 c1 c2 read +permutation tid1 tidsucceed2 c1 c2 read +# test that a rollback on s1 has s2 perform the update on the original row +permutation tid1 tid2 r1 c2 read permutation simplepartupdate conditionalpartupdate c1 c2 read_part permutation simplepartupdate complexpartupdate c1 c2 read_part From ac06ea8f7b6c6aeedef9bff5fb30f606f6054a55 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 17 Sep 2025 12:19:15 +1200 Subject: [PATCH 89/96] Add missing EPQ recheck for TID Range Scan The EvalPlanQual recheck for TID Range Scan wasn't rechecking the TID qual still passed after following update chains. This could result in tuples being updated or deleted by plans using TID Range Scans where the ctid of the new (updated) tuple no longer matches the clause of the scan. This isn't desired behavior, and isn't consistent with what would happen if the chosen plan had used an Index or Seq Scan, and that could lead to hard to predict behavior for scans that contain TID quals and other quals as the planner has freedom to choose TID Range or some other non-TID scan method for such queries, and the chosen plan could change at any moment. Here we fix this by properly implementing the recheck function for TID Range Scans. Backpatch to 14, where TID Range Scans were added Reported-by: Sophie Alpert Author: Sophie Alpert Author: David Rowley Reviewed-by: David Rowley Reviewed-by: Chao Li Discussion: https://postgr.es/m/4a6268ff-3340-453a-9bf5-c98d51a6f729@app.fastmail.com Backpatch-through: 14 --- src/backend/executor/nodeTidrangescan.c | 10 ++++++++ .../isolation/expected/eval-plan-qual.out | 23 +++++++++++++++++++ src/test/isolation/specs/eval-plan-qual.spec | 5 +++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c index 26f7420b64b0e..1bce8d6cbfe6b 100644 --- a/src/backend/executor/nodeTidrangescan.c +++ b/src/backend/executor/nodeTidrangescan.c @@ -274,6 +274,16 @@ TidRangeNext(TidRangeScanState *node) static bool TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot) { + if (!TidRangeEval(node)) + return false; + + Assert(ItemPointerIsValid(&slot->tts_tid)); + + /* Recheck the ctid is still within range */ + if (ItemPointerCompare(&slot->tts_tid, &node->trss_mintid) < 0 || + ItemPointerCompare(&slot->tts_tid, &node->trss_maxtid) > 0) + return false; + return true; } diff --git a/src/test/isolation/expected/eval-plan-qual.out b/src/test/isolation/expected/eval-plan-qual.out index 2cba44f2bde3f..3d31d0f84e590 100644 --- a/src/test/isolation/expected/eval-plan-qual.out +++ b/src/test/isolation/expected/eval-plan-qual.out @@ -1265,6 +1265,29 @@ savings | 600| 1200 (2 rows) +starting permutation: tidrange1 tidrange2 c1 c2 read +step tidrange1: UPDATE accounts SET balance = balance + 100 WHERE ctid BETWEEN '(0,1)' AND '(0,1)' RETURNING accountid, balance; +accountid|balance +---------+------- +checking | 700 +(1 row) + +step tidrange2: UPDATE accounts SET balance = balance + 200 WHERE ctid BETWEEN '(0,1)' AND '(0,1)' RETURNING accountid, balance; +step c1: COMMIT; +step tidrange2: <... completed> +accountid|balance +---------+------- +(0 rows) + +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid|balance|balance2 +---------+-------+-------- +checking | 700| 1400 +savings | 600| 1200 +(2 rows) + + starting permutation: tid1 tid2 r1 c2 read step tid1: UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; accountid|balance diff --git a/src/test/isolation/specs/eval-plan-qual.spec b/src/test/isolation/specs/eval-plan-qual.spec index b8983ea215f18..c6eee68558685 100644 --- a/src/test/isolation/specs/eval-plan-qual.spec +++ b/src/test/isolation/specs/eval-plan-qual.spec @@ -99,8 +99,9 @@ step upsert1 { WHERE NOT EXISTS (SELECT 1 FROM upsert); } -# Tests for Tid Scan +# Tests for Tid / Tid Range Scan step tid1 { UPDATE accounts SET balance = balance + 100 WHERE ctid = '(0,1)' RETURNING accountid, balance; } +step tidrange1 { UPDATE accounts SET balance = balance + 100 WHERE ctid BETWEEN '(0,1)' AND '(0,1)' RETURNING accountid, balance; } # tests with table p check inheritance cases: # readp1/writep1/readp2 tests a bug where nodeLockRows did the wrong thing @@ -245,6 +246,7 @@ step wrtwcte { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; } step wrjt { UPDATE jointest SET data = 42 WHERE id = 7; } step tid2 { UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' RETURNING accountid, balance; } +step tidrange2 { UPDATE accounts SET balance = balance + 200 WHERE ctid BETWEEN '(0,1)' AND '(0,1)' RETURNING accountid, balance; } # here, recheck succeeds; (0,3) is the id that step tid1 will assign step tidsucceed2 { UPDATE accounts SET balance = balance + 200 WHERE ctid = '(0,1)' OR ctid = '(0,3)' RETURNING accountid, balance; } @@ -401,6 +403,7 @@ permutation wrjt selectresultforupdate c2 c1 permutation wrtwcte multireadwcte c1 c2 permutation tid1 tid2 c1 c2 read permutation tid1 tidsucceed2 c1 c2 read +permutation tidrange1 tidrange2 c1 c2 read # test that a rollback on s1 has s2 perform the update on the original row permutation tid1 tid2 r1 c2 read From 158c48303e8ae0248d7d95b60b56dd67e7033c48 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 17 Sep 2025 09:33:32 +0900 Subject: [PATCH 90/96] Fix shared memory calculation size of PgAioCtl The shared memory size was calculated based on an offset of io_handles, which is itself a pointer included in the structure. We tend to overestimate the shared memory size overall, so this was unlikely an issue in practice, but let's be correct and use the full size of the structure in the calculation, so as the pointer for io_handles is included. Oversight in da7226993fd4. Author: Madhukar Prasad Reviewed-by: Andres Freund Reviewed-by: Matthias van de Meent Discussion: https://postgr.es/m/CAKi+wrbC2dTzh_vKJoAZXV5wqTbhY0n4wRNpCjJ=e36aoo0kFw@mail.gmail.com Backpatch-through: 18 --- src/backend/storage/aio/aio_init.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/backend/storage/aio/aio_init.c b/src/backend/storage/aio/aio_init.c index 885c3940c6626..54ab84dd6f03a 100644 --- a/src/backend/storage/aio/aio_init.c +++ b/src/backend/storage/aio/aio_init.c @@ -30,12 +30,8 @@ static Size AioCtlShmemSize(void) { - Size sz; - /* pgaio_ctl itself */ - sz = offsetof(PgAioCtl, io_handles); - - return sz; + return sizeof(PgAioCtl); } static uint32 From 39f67d9b554fcd6dfb6c4f02a4af757eecd1fecf Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 17 Sep 2025 10:15:13 +0900 Subject: [PATCH 91/96] injection_points: Fix incrementation of variable-numbered stats The pending entry was not used when incrementing its data, directly manipulating the shared memory pointer, without even locking it. This could mean losing statistics under concurrent activity. The flush callback was a no-op. This code serves as a base template for extensions for the custom cumulative statistics, so let's be clean and use a pending entry for the incrementations, whose data is then flushed to the corresponding entry in the shared hashtable when all the stats are reported, in its own flush callback. Author: Sami Imseih Discussion: https://postgr.es/m/CAA5RZ0v0U0yhPbY+bqChomkPbyUrRQ3rQXnZf_SB-svDiQOpgQ@mail.gmail.com Backpatch-through: 18 --- src/test/modules/injection_points/injection_stats.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/modules/injection_points/injection_stats.c b/src/test/modules/injection_points/injection_stats.c index e3947b23ba573..ca8df4ad217ab 100644 --- a/src/test/modules/injection_points/injection_stats.c +++ b/src/test/modules/injection_points/injection_stats.c @@ -164,8 +164,7 @@ void pgstat_report_inj(const char *name) { PgStat_EntryRef *entry_ref; - PgStatShared_InjectionPoint *shstatent; - PgStat_StatInjEntry *statent; + PgStat_StatInjEntry *pending; /* leave if disabled */ if (!inj_stats_loaded || !inj_stats_enabled) @@ -174,11 +173,10 @@ pgstat_report_inj(const char *name) entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_INJECTION, InvalidOid, PGSTAT_INJ_IDX(name), NULL); - shstatent = (PgStatShared_InjectionPoint *) entry_ref->shared_stats; - statent = &shstatent->stats; + pending = (PgStat_StatInjEntry *) entry_ref->pending; - /* Update the injection point statistics */ - statent->numcalls++; + /* Update the injection point pending statistics */ + pending->numcalls++; } /* From 0951942bba25f85ad29a4f096ed51a356652b5a2 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Wed, 17 Sep 2025 12:00:16 +1200 Subject: [PATCH 92/96] jit: Fix type used for Datum values in LLVM IR. Commit 2a600a93 made Datum 8 bytes wide everywhere. It was no longer appropriate to use TypeSizeT on 32 bit systems, and JIT compilation would fail with various type check errors. Introduce a separate LLVMTypeRef with the name TypeDatum. TypeSizeT is still used in some places for actual size_t values. Reported-by: Dmitry Mityugov Reviewed-by: Tom Lane Tested-by: Dmitry Mityugov Discussion: https://postgr.es/m/0a9f0be59171c2e8f1b3bc10f4fcf267%40postgrespro.ru --- src/backend/jit/llvm/llvmjit.c | 2 + src/backend/jit/llvm/llvmjit_deform.c | 10 +- src/backend/jit/llvm/llvmjit_expr.c | 150 +++++++++++++------------- src/backend/jit/llvm/llvmjit_types.c | 1 + src/include/jit/llvmjit.h | 1 + src/include/jit/llvmjit_emit.h | 11 +- 6 files changed, 94 insertions(+), 81 deletions(-) diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index 46511624f0166..e978b996baede 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -54,6 +54,7 @@ typedef struct LLVMJitHandle /* types & functions commonly needed for JITing */ LLVMTypeRef TypeSizeT; +LLVMTypeRef TypeDatum; LLVMTypeRef TypeParamBool; LLVMTypeRef TypeStorageBool; LLVMTypeRef TypePGFunction; @@ -1011,6 +1012,7 @@ llvm_create_types(void) LLVMDisposeMemoryBuffer(buf); TypeSizeT = llvm_pg_var_type("TypeSizeT"); + TypeDatum = llvm_pg_var_type("TypeDatum"); TypeParamBool = load_return_type(llvm_types_module, "FunctionReturningBool"); TypeStorageBool = llvm_pg_var_type("TypeStorageBool"); TypePGFunction = llvm_pg_var_type("TypePGFunction"); diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c index c562edd094bb2..9791073faf9d3 100644 --- a/src/backend/jit/llvm/llvmjit_deform.c +++ b/src/backend/jit/llvm/llvmjit_deform.c @@ -479,8 +479,8 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, l_gep(b, LLVMInt8TypeInContext(lc), v_tts_nulls, &l_attno, 1, "")); /* store zero datum */ LLVMBuildStore(b, - l_sizet_const(0), - l_gep(b, TypeSizeT, v_tts_values, &l_attno, 1, "")); + l_datum_const(0), + l_gep(b, TypeDatum, v_tts_values, &l_attno, 1, "")); LLVMBuildBr(b, b_next); attguaranteedalign = false; @@ -644,7 +644,7 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, } /* compute address to store value at */ - v_resultp = l_gep(b, TypeSizeT, v_tts_values, &l_attno, 1, ""); + v_resultp = l_gep(b, TypeDatum, v_tts_values, &l_attno, 1, ""); /* store null-byte (false) */ LLVMBuildStore(b, l_int8_const(lc, 0), @@ -663,7 +663,7 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, v_tmp_loaddata = LLVMBuildPointerCast(b, v_attdatap, vartypep, ""); v_tmp_loaddata = l_load(b, vartype, v_tmp_loaddata, "attr_byval"); - v_tmp_loaddata = LLVMBuildZExt(b, v_tmp_loaddata, TypeSizeT, ""); + v_tmp_loaddata = LLVMBuildZExt(b, v_tmp_loaddata, TypeDatum, ""); LLVMBuildStore(b, v_tmp_loaddata, v_resultp); } @@ -675,7 +675,7 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, v_tmp_loaddata = LLVMBuildPtrToInt(b, v_attdatap, - TypeSizeT, + TypeDatum, "attr_ptr"); LLVMBuildStore(b, v_tmp_loaddata, v_resultp); } diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 890bcb0b0a79d..712b35df7e581 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -316,7 +316,7 @@ llvm_compile_expr(ExprState *state) op = &state->steps[opno]; opcode = ExecEvalStepOp(state, op); - v_resvaluep = l_ptr_const(op->resvalue, l_ptr(TypeSizeT)); + v_resvaluep = l_ptr_const(op->resvalue, l_ptr(TypeDatum)); v_resnullp = l_ptr_const(op->resnull, l_ptr(TypeStorageBool)); switch (opcode) @@ -326,7 +326,7 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_tmpisnull; LLVMValueRef v_tmpvalue; - v_tmpvalue = l_load(b, TypeSizeT, v_tmpvaluep, ""); + v_tmpvalue = l_load(b, TypeDatum, v_tmpvaluep, ""); v_tmpisnull = l_load(b, TypeStorageBool, v_tmpisnullp, ""); LLVMBuildStore(b, v_tmpisnull, v_isnullp); @@ -336,7 +336,7 @@ llvm_compile_expr(ExprState *state) } case EEOP_DONE_NO_RETURN: - LLVMBuildRet(b, l_sizet_const(0)); + LLVMBuildRet(b, l_datum_const(0)); break; case EEOP_INNER_FETCHSOME: @@ -478,7 +478,7 @@ llvm_compile_expr(ExprState *state) } v_attnum = l_int32_const(lc, op->d.var.attnum); - value = l_load_gep1(b, TypeSizeT, v_values, v_attnum, ""); + value = l_load_gep1(b, TypeDatum, v_values, v_attnum, ""); isnull = l_load_gep1(b, TypeStorageBool, v_nulls, v_attnum, ""); LLVMBuildStore(b, value, v_resvaluep); LLVMBuildStore(b, isnull, v_resnullp); @@ -562,13 +562,13 @@ llvm_compile_expr(ExprState *state) /* load data */ v_attnum = l_int32_const(lc, op->d.assign_var.attnum); - v_value = l_load_gep1(b, TypeSizeT, v_values, v_attnum, ""); + v_value = l_load_gep1(b, TypeDatum, v_values, v_attnum, ""); v_isnull = l_load_gep1(b, TypeStorageBool, v_nulls, v_attnum, ""); /* compute addresses of targets */ v_resultnum = l_int32_const(lc, op->d.assign_var.resultnum); v_rvaluep = l_gep(b, - TypeSizeT, + TypeDatum, v_resultvalues, &v_resultnum, 1, ""); v_risnullp = l_gep(b, @@ -595,13 +595,13 @@ llvm_compile_expr(ExprState *state) size_t resultnum = op->d.assign_tmp.resultnum; /* load data */ - v_value = l_load(b, TypeSizeT, v_tmpvaluep, ""); + v_value = l_load(b, TypeDatum, v_tmpvaluep, ""); v_isnull = l_load(b, TypeStorageBool, v_tmpisnullp, ""); /* compute addresses of targets */ v_resultnum = l_int32_const(lc, resultnum); v_rvaluep = - l_gep(b, TypeSizeT, v_resultvalues, &v_resultnum, 1, ""); + l_gep(b, TypeDatum, v_resultvalues, &v_resultnum, 1, ""); v_risnullp = l_gep(b, TypeStorageBool, v_resultnulls, &v_resultnum, 1, ""); @@ -650,7 +650,7 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_constvalue, v_constnull; - v_constvalue = l_sizet_const(op->d.constval.value); + v_constvalue = l_datum_const(op->d.constval.value); v_constnull = l_sbool_const(op->d.constval.isnull); LLVMBuildStore(b, v_constvalue, v_resvaluep); @@ -798,7 +798,7 @@ llvm_compile_expr(ExprState *state) LLVMBuildStore(b, l_sbool_const(0), v_boolanynullp); v_boolnull = l_load(b, TypeStorageBool, v_resnullp, ""); - v_boolvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_boolvalue = l_load(b, TypeDatum, v_resvaluep, ""); /* check if current input is NULL */ LLVMBuildCondBr(b, @@ -818,7 +818,7 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_boolcheckfalse); LLVMBuildCondBr(b, LLVMBuildICmp(b, LLVMIntEQ, v_boolvalue, - l_sizet_const(0), ""), + l_datum_const(0), ""), b_boolisfalse, b_boolcont); @@ -846,7 +846,7 @@ llvm_compile_expr(ExprState *state) /* set resnull to true */ LLVMBuildStore(b, l_sbool_const(1), v_resnullp); /* reset resvalue */ - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildBr(b, opblocks[opno + 1]); break; @@ -889,7 +889,7 @@ llvm_compile_expr(ExprState *state) if (opcode == EEOP_BOOL_OR_STEP_FIRST) LLVMBuildStore(b, l_sbool_const(0), v_boolanynullp); v_boolnull = l_load(b, TypeStorageBool, v_resnullp, ""); - v_boolvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_boolvalue = l_load(b, TypeDatum, v_resvaluep, ""); LLVMBuildCondBr(b, LLVMBuildICmp(b, LLVMIntEQ, v_boolnull, @@ -908,7 +908,7 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_boolchecktrue); LLVMBuildCondBr(b, LLVMBuildICmp(b, LLVMIntEQ, v_boolvalue, - l_sizet_const(1), ""), + l_datum_const(1), ""), b_boolistrue, b_boolcont); @@ -936,7 +936,7 @@ llvm_compile_expr(ExprState *state) /* set resnull to true */ LLVMBuildStore(b, l_sbool_const(1), v_resnullp); /* reset resvalue */ - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildBr(b, opblocks[opno + 1]); break; @@ -948,13 +948,13 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_negbool; /* compute !boolvalue */ - v_boolvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_boolvalue = l_load(b, TypeDatum, v_resvaluep, ""); v_negbool = LLVMBuildZExt(b, LLVMBuildICmp(b, LLVMIntEQ, v_boolvalue, - l_sizet_const(0), + l_datum_const(0), ""), - TypeSizeT, ""); + TypeDatum, ""); /* * Store it back in resvalue. We can ignore resnull here; @@ -977,7 +977,7 @@ llvm_compile_expr(ExprState *state) b_qualfail = l_bb_before_v(opblocks[opno + 1], "op.%d.qualfail", opno); - v_resvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_resvalue = l_load(b, TypeDatum, v_resvaluep, ""); v_resnull = l_load(b, TypeStorageBool, v_resnullp, ""); v_nullorfalse = @@ -985,7 +985,7 @@ llvm_compile_expr(ExprState *state) LLVMBuildICmp(b, LLVMIntEQ, v_resnull, l_sbool_const(1), ""), LLVMBuildICmp(b, LLVMIntEQ, v_resvalue, - l_sizet_const(0), ""), + l_datum_const(0), ""), ""); LLVMBuildCondBr(b, @@ -998,7 +998,7 @@ llvm_compile_expr(ExprState *state) /* set resnull to false */ LLVMBuildStore(b, l_sbool_const(0), v_resnullp); /* set resvalue to false */ - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); /* and jump out */ LLVMBuildBr(b, opblocks[op->d.qualexpr.jumpdone]); break; @@ -1051,7 +1051,7 @@ llvm_compile_expr(ExprState *state) /* Transfer control if current result is null or false */ - v_resvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_resvalue = l_load(b, TypeDatum, v_resvaluep, ""); v_resnull = l_load(b, TypeStorageBool, v_resnullp, ""); v_nullorfalse = @@ -1059,7 +1059,7 @@ llvm_compile_expr(ExprState *state) LLVMBuildICmp(b, LLVMIntEQ, v_resnull, l_sbool_const(1), ""), LLVMBuildICmp(b, LLVMIntEQ, v_resvalue, - l_sizet_const(0), ""), + l_datum_const(0), ""), ""); LLVMBuildCondBr(b, @@ -1078,8 +1078,8 @@ llvm_compile_expr(ExprState *state) LLVMBuildSelect(b, LLVMBuildICmp(b, LLVMIntEQ, v_resnull, l_sbool_const(1), ""), - l_sizet_const(1), - l_sizet_const(0), + l_datum_const(1), + l_datum_const(0), ""); LLVMBuildStore(b, v_resvalue, v_resvaluep); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); @@ -1097,8 +1097,8 @@ llvm_compile_expr(ExprState *state) LLVMBuildSelect(b, LLVMBuildICmp(b, LLVMIntEQ, v_resnull, l_sbool_const(1), ""), - l_sizet_const(0), - l_sizet_const(1), + l_datum_const(0), + l_datum_const(1), ""); LLVMBuildStore(b, v_resvalue, v_resvaluep); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); @@ -1148,11 +1148,11 @@ llvm_compile_expr(ExprState *state) if (opcode == EEOP_BOOLTEST_IS_TRUE || opcode == EEOP_BOOLTEST_IS_FALSE) { - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); } else { - LLVMBuildStore(b, l_sizet_const(1), v_resvaluep); + LLVMBuildStore(b, l_datum_const(1), v_resvaluep); } LLVMBuildBr(b, opblocks[opno + 1]); @@ -1170,14 +1170,14 @@ llvm_compile_expr(ExprState *state) else { LLVMValueRef v_value = - l_load(b, TypeSizeT, v_resvaluep, ""); + l_load(b, TypeDatum, v_resvaluep, ""); v_value = LLVMBuildZExt(b, LLVMBuildICmp(b, LLVMIntEQ, v_value, - l_sizet_const(0), + l_datum_const(0), ""), - TypeSizeT, ""); + TypeDatum, ""); LLVMBuildStore(b, v_value, v_resvaluep); } LLVMBuildBr(b, opblocks[opno + 1]); @@ -1279,11 +1279,11 @@ llvm_compile_expr(ExprState *state) v_casenull; v_casevaluep = l_ptr_const(op->d.casetest.value, - l_ptr(TypeSizeT)); + l_ptr(TypeDatum)); v_casenullp = l_ptr_const(op->d.casetest.isnull, l_ptr(TypeStorageBool)); - v_casevalue = l_load(b, TypeSizeT, v_casevaluep, ""); + v_casevalue = l_load(b, TypeDatum, v_casevaluep, ""); v_casenull = l_load(b, TypeStorageBool, v_casenullp, ""); LLVMBuildStore(b, v_casevalue, v_resvaluep); LLVMBuildStore(b, v_casenull, v_resnullp); @@ -1345,9 +1345,9 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_notnull); v_valuep = l_ptr_const(op->d.make_readonly.value, - l_ptr(TypeSizeT)); + l_ptr(TypeDatum)); - v_value = l_load(b, TypeSizeT, v_valuep, ""); + v_value = l_load(b, TypeDatum, v_valuep, ""); v_params[0] = v_value; v_ret = @@ -1415,11 +1415,11 @@ llvm_compile_expr(ExprState *state) b_calloutput); LLVMPositionBuilderAtEnd(b, b_skipoutput); - v_output_skip = l_sizet_const(0); + v_output_skip = l_datum_const(0); LLVMBuildBr(b, b_input); LLVMPositionBuilderAtEnd(b, b_calloutput); - v_resvalue = l_load(b, TypeSizeT, v_resvaluep, ""); + v_resvalue = l_load(b, TypeDatum, v_resvaluep, ""); /* set arg[0] */ LLVMBuildStore(b, @@ -1449,7 +1449,7 @@ llvm_compile_expr(ExprState *state) incoming_values[1] = v_output; incoming_blocks[1] = b_calloutput; - v_output = LLVMBuildPhi(b, TypeSizeT, "output"); + v_output = LLVMBuildPhi(b, TypeDatum, "output"); LLVMAddIncoming(v_output, incoming_values, incoming_blocks, lengthof(incoming_blocks)); @@ -1463,7 +1463,7 @@ llvm_compile_expr(ExprState *state) { LLVMBuildCondBr(b, LLVMBuildICmp(b, LLVMIntEQ, v_output, - l_sizet_const(0), ""), + l_datum_const(0), ""), opblocks[opno + 1], b_inputcall); } @@ -1564,9 +1564,9 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_bothargnull); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); if (opcode == EEOP_NOT_DISTINCT) - LLVMBuildStore(b, l_sizet_const(1), v_resvaluep); + LLVMBuildStore(b, l_datum_const(1), v_resvaluep); else - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildBr(b, opblocks[opno + 1]); @@ -1574,9 +1574,9 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_anyargnull); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); if (opcode == EEOP_NOT_DISTINCT) - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); else - LLVMBuildStore(b, l_sizet_const(1), v_resvaluep); + LLVMBuildStore(b, l_datum_const(1), v_resvaluep); LLVMBuildBr(b, opblocks[opno + 1]); /* neither argument is null: compare */ @@ -1592,8 +1592,8 @@ llvm_compile_expr(ExprState *state) LLVMBuildZExt(b, LLVMBuildICmp(b, LLVMIntEQ, v_result, - l_sizet_const(0), ""), - TypeSizeT, ""); + l_datum_const(0), ""), + TypeDatum, ""); } LLVMBuildStore(b, v_fcinfo_isnull, v_resnullp); @@ -1691,7 +1691,7 @@ llvm_compile_expr(ExprState *state) ""), LLVMBuildICmp(b, LLVMIntEQ, v_retval, - l_sizet_const(1), + l_datum_const(1), ""), ""); LLVMBuildCondBr(b, v_argsequal, b_argsequal, b_hasnull); @@ -1699,7 +1699,7 @@ llvm_compile_expr(ExprState *state) /* build block setting result to NULL, if args are equal */ LLVMPositionBuilderAtEnd(b, b_argsequal); LLVMBuildStore(b, l_sbool_const(1), v_resnullp); - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildBr(b, opblocks[opno + 1]); break; @@ -1755,7 +1755,7 @@ llvm_compile_expr(ExprState *state) LLVMPositionBuilderAtEnd(b, b_isnull); - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildStore(b, l_sbool_const(1), v_resnullp); LLVMBuildBr(b, opblocks[op->d.returningexpr.jumpdone]); @@ -1861,7 +1861,7 @@ llvm_compile_expr(ExprState *state) LLVMBuildICmp(b, LLVMIntEQ, v_retval, - l_sizet_const(0), ""), + l_datum_const(0), ""), opblocks[opno + 1], opblocks[op->d.rowcompare_step.jumpdone]); @@ -1891,7 +1891,7 @@ llvm_compile_expr(ExprState *state) */ v_cmpresult = LLVMBuildTrunc(b, - l_load(b, TypeSizeT, v_resvaluep, ""), + l_load(b, TypeDatum, v_resvaluep, ""), LLVMInt32TypeInContext(lc), ""); switch (cmptype) @@ -1920,7 +1920,7 @@ llvm_compile_expr(ExprState *state) v_cmpresult, l_int32_const(lc, 0), ""); - v_result = LLVMBuildZExt(b, v_result, TypeSizeT, ""); + v_result = LLVMBuildZExt(b, v_result, TypeDatum, ""); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); LLVMBuildStore(b, v_result, v_resvaluep); @@ -1961,11 +1961,11 @@ llvm_compile_expr(ExprState *state) v_casenull; v_casevaluep = l_ptr_const(op->d.casetest.value, - l_ptr(TypeSizeT)); + l_ptr(TypeDatum)); v_casenullp = l_ptr_const(op->d.casetest.isnull, l_ptr(TypeStorageBool)); - v_casevalue = l_load(b, TypeSizeT, v_casevaluep, ""); + v_casevalue = l_load(b, TypeDatum, v_casevaluep, ""); v_casenull = l_load(b, TypeStorageBool, v_casenullp, ""); LLVMBuildStore(b, v_casevalue, v_resvaluep); LLVMBuildStore(b, v_casenull, v_resnullp); @@ -2014,7 +2014,7 @@ llvm_compile_expr(ExprState *state) { LLVMValueRef v_initvalue; - v_initvalue = l_sizet_const(op->d.hashdatum_initvalue.init_value); + v_initvalue = l_datum_const(op->d.hashdatum_initvalue.init_value); LLVMBuildStore(b, v_initvalue, v_resvaluep); LLVMBuildStore(b, l_sbool_const(0), v_resnullp); @@ -2053,24 +2053,24 @@ llvm_compile_expr(ExprState *state) LLVMValueRef tmp; tmp = l_ptr_const(&op->d.hashdatum.iresult->value, - l_ptr(TypeSizeT)); + l_ptr(TypeDatum)); /* * Fetch the previously hashed value from where the * previous hash operation stored it. */ - v_prevhash = l_load(b, TypeSizeT, tmp, "prevhash"); + v_prevhash = l_load(b, TypeDatum, tmp, "prevhash"); /* * Rotate bits left by 1 bit. Be careful not to - * overflow uint32 when working with size_t. + * overflow uint32 when working with Datum. */ - v_tmp1 = LLVMBuildShl(b, v_prevhash, l_sizet_const(1), + v_tmp1 = LLVMBuildShl(b, v_prevhash, l_datum_const(1), ""); v_tmp1 = LLVMBuildAnd(b, v_tmp1, - l_sizet_const(0xffffffff), ""); + l_datum_const(0xffffffff), ""); v_tmp2 = LLVMBuildLShr(b, v_prevhash, - l_sizet_const(31), ""); + l_datum_const(31), ""); v_prevhash = LLVMBuildOr(b, v_tmp1, v_tmp2, "rotatedhash"); } @@ -2113,7 +2113,7 @@ llvm_compile_expr(ExprState *state) * the NULL result and goto jumpdone. */ LLVMBuildStore(b, l_sbool_const(1), v_resnullp); - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); LLVMBuildBr(b, opblocks[op->d.hashdatum.jumpdone]); } else @@ -2145,7 +2145,7 @@ llvm_compile_expr(ExprState *state) * Store a zero Datum when the Datum to hash is * NULL */ - LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); } LLVMBuildBr(b, opblocks[opno + 1]); @@ -2178,24 +2178,24 @@ llvm_compile_expr(ExprState *state) LLVMValueRef tmp; tmp = l_ptr_const(&op->d.hashdatum.iresult->value, - l_ptr(TypeSizeT)); + l_ptr(TypeDatum)); /* * Fetch the previously hashed value from where the * previous hash operation stored it. */ - v_prevhash = l_load(b, TypeSizeT, tmp, "prevhash"); + v_prevhash = l_load(b, TypeDatum, tmp, "prevhash"); /* * Rotate bits left by 1 bit. Be careful not to - * overflow uint32 when working with size_t. + * overflow uint32 when working with Datum. */ - v_tmp1 = LLVMBuildShl(b, v_prevhash, l_sizet_const(1), + v_tmp1 = LLVMBuildShl(b, v_prevhash, l_datum_const(1), ""); v_tmp1 = LLVMBuildAnd(b, v_tmp1, - l_sizet_const(0xffffffff), ""); + l_datum_const(0xffffffff), ""); v_tmp2 = LLVMBuildLShr(b, v_prevhash, - l_sizet_const(31), ""); + l_datum_const(31), ""); v_prevhash = LLVMBuildOr(b, v_tmp1, v_tmp2, "rotatedhash"); } @@ -2373,7 +2373,7 @@ llvm_compile_expr(ExprState *state) v_aggno = l_int32_const(lc, op->d.aggref.aggno); /* load agg value / null */ - value = l_load_gep1(b, TypeSizeT, v_aggvalues, v_aggno, "aggvalue"); + value = l_load_gep1(b, TypeDatum, v_aggvalues, v_aggno, "aggvalue"); isnull = l_load_gep1(b, TypeStorageBool, v_aggnulls, v_aggno, "aggnull"); /* and store result */ @@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state) v_wfuncno = l_load(b, LLVMInt32TypeInContext(lc), v_wfuncnop, "v_wfuncno"); /* load window func value / null */ - value = l_load_gep1(b, TypeSizeT, v_aggvalues, v_wfuncno, + value = l_load_gep1(b, TypeDatum, v_aggvalues, v_wfuncno, "windowvalue"); isnull = l_load_gep1(b, TypeStorageBool, v_aggnulls, v_wfuncno, "windownull"); @@ -2585,8 +2585,8 @@ llvm_compile_expr(ExprState *state) LLVMBuildCondBr(b, LLVMBuildICmp(b, LLVMIntEQ, - LLVMBuildPtrToInt(b, v_pergroup_allaggs, TypeSizeT, ""), - l_sizet_const(0), ""), + LLVMBuildPtrToInt(b, v_pergroup_allaggs, TypeDatum, ""), + l_datum_const(0), ""), opblocks[jumpnull], opblocks[opno + 1]); break; @@ -2788,7 +2788,7 @@ llvm_compile_expr(ExprState *state) "transnullp"); LLVMBuildStore(b, l_load(b, - TypeSizeT, + TypeDatum, v_transvaluep, "transvalue"), l_funcvaluep(b, v_fcinfo, 0)); @@ -2826,7 +2826,7 @@ llvm_compile_expr(ExprState *state) b_nocall = l_bb_before_v(opblocks[opno + 1], "op.%d.transnocall", opno); - v_transvalue = l_load(b, TypeSizeT, v_transvaluep, ""); + v_transvalue = l_load(b, TypeDatum, v_transvaluep, ""); v_transnull = l_load(b, TypeStorageBool, v_transnullp, ""); /* diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index dbe0282e98f4b..167cd554b9c07 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -47,6 +47,7 @@ */ PGFunction TypePGFunction; size_t TypeSizeT; +Datum TypeDatum; bool TypeStorageBool; ExecEvalSubroutine TypeExecEvalSubroutine; diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h index 5038cf33e3fe9..b3c75022f55ce 100644 --- a/src/include/jit/llvmjit.h +++ b/src/include/jit/llvmjit.h @@ -74,6 +74,7 @@ typedef struct LLVMJitContext extern PGDLLIMPORT LLVMTypeRef TypeParamBool; extern PGDLLIMPORT LLVMTypeRef TypePGFunction; extern PGDLLIMPORT LLVMTypeRef TypeSizeT; +extern PGDLLIMPORT LLVMTypeRef TypeDatum; extern PGDLLIMPORT LLVMTypeRef TypeStorageBool; extern PGDLLIMPORT LLVMTypeRef StructNullableDatum; diff --git a/src/include/jit/llvmjit_emit.h b/src/include/jit/llvmjit_emit.h index df5a9fc85007a..0e57a332b6ef3 100644 --- a/src/include/jit/llvmjit_emit.h +++ b/src/include/jit/llvmjit_emit.h @@ -86,6 +86,15 @@ l_sizet_const(size_t i) return LLVMConstInt(TypeSizeT, i, false); } +/* + * Emit constant integer. + */ +static inline LLVMValueRef +l_datum_const(Datum i) +{ + return LLVMConstInt(TypeDatum, i, false); +} + /* * Emit constant boolean, as used for storage (e.g. global vars, structs). */ @@ -313,7 +322,7 @@ l_funcnull(LLVMBuilderRef b, LLVMValueRef v_fcinfo, size_t argno) static inline LLVMValueRef l_funcvalue(LLVMBuilderRef b, LLVMValueRef v_fcinfo, size_t argno) { - return l_load(b, TypeSizeT, l_funcvaluep(b, v_fcinfo, argno), ""); + return l_load(b, TypeDatum, l_funcvaluep(b, v_fcinfo, argno), ""); } #endif /* USE_LLVM */ From b0cc0a71e0a0a760f54c72edb8cd000e4555442b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 17 Sep 2025 16:32:57 -0400 Subject: [PATCH 93/96] Calculate agglevelsup correctly when Aggref contains a CTE. If an aggregate function call contains a sub-select that has an RTE referencing a CTE outside the aggregate, we must treat that reference like a Var referencing the CTE's query level for purposes of determining the aggregate's level. Otherwise we might reach the nonsensical conclusion that the aggregate should be evaluated at some query level higher than the CTE, ending in a planner error or a broken plan tree that causes executor failures. Bug: #19055 Reported-by: BugForge Author: Tom Lane Discussion: https://postgr.es/m/19055-6970cfa8556a394d@postgresql.org Backpatch-through: 13 --- src/backend/parser/parse_agg.c | 28 +++++++++++++++++++++++- src/test/regress/expected/with.out | 34 ++++++++++++++++++++++++++++++ src/test/regress/sql/with.sql | 14 ++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 0ac8966e30ff3..3254c83cc6cdb 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -791,6 +791,32 @@ check_agg_arguments_walker(Node *node, parser_errposition(context->pstate, ((WindowFunc *) node)->location))); } + + if (IsA(node, RangeTblEntry)) + { + /* + * CTE references act similarly to Vars of the CTE's level. Without + * this we might conclude that the Agg can be evaluated above the CTE, + * leading to trouble. + */ + RangeTblEntry *rte = (RangeTblEntry *) node; + + if (rte->rtekind == RTE_CTE) + { + int ctelevelsup = rte->ctelevelsup; + + /* convert levelsup to frame of reference of original query */ + ctelevelsup -= context->sublevels_up; + /* ignore local CTEs of subqueries */ + if (ctelevelsup >= 0) + { + if (context->min_varlevel < 0 || + context->min_varlevel > ctelevelsup) + context->min_varlevel = ctelevelsup; + } + } + return false; /* allow range_table_walker to continue */ + } if (IsA(node, Query)) { /* Recurse into subselects */ @@ -800,7 +826,7 @@ check_agg_arguments_walker(Node *node, result = query_tree_walker((Query *) node, check_agg_arguments_walker, context, - 0); + QTW_EXAMINE_RTES_BEFORE); context->sublevels_up--; return result; } diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index f015e9972761a..c3932c7b94c0b 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -2297,6 +2297,40 @@ from int4_tbl; -2147483647 (5 rows) +-- +-- test for bug #19055: interaction of WITH with aggregates +-- +-- The reference to cte1 must determine the aggregate's level, +-- even though it contains no Vars referencing cte1 +explain (verbose, costs off) +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.int4_tbl i4 + Output: i4.f1, (SubPlan 2) + SubPlan 2 + -> Aggregate + Output: count((InitPlan 1).col1) + InitPlan 1 + -> Result + Output: i4.f1 + -> Result +(9 rows) + +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + f1 | ss +-------------+---- + 0 | 1 + 123456 | 1 + -123456 | 1 + 2147483647 | 1 + -2147483647 | 1 +(5 rows) + -- -- test for nested-recursive-WITH bug -- diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index b1cae1612904b..d88d5abb91a11 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -1097,6 +1097,20 @@ select ( with cte(foo) as ( values(f1) ) values((select foo from cte)) ) from int4_tbl; +-- +-- test for bug #19055: interaction of WITH with aggregates +-- +-- The reference to cte1 must determine the aggregate's level, +-- even though it contains no Vars referencing cte1 +explain (verbose, costs off) +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + +select f1, (with cte1(x,y) as (select 1,2) + select count((select i4.f1 from cte1))) as ss +from int4_tbl i4; + -- -- test for nested-recursive-WITH bug -- From 0110e2ec5c0f230beb439885bd1f8505e783e742 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 17 Sep 2025 20:28:43 -0400 Subject: [PATCH 94/96] Mark shared buffer lookup table HASH_FIXED_SIZE StrategyInitialize() calls InitBufTable() with maximum number of entries that the buffer lookup table can ever have. Thus there should not be any need to allocate more element after initialization. Hence mark the hash table as fixed sized. Author: Ashutosh Bapat Discussion: https://postgr.es/m/CAExHW5v0jh3F_wj86yC=qBfWk0uiT94qy=Z41uzAHLHh0SerRA@mail.gmail.com --- src/backend/storage/buffer/buf_table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/storage/buffer/buf_table.c b/src/backend/storage/buffer/buf_table.c index a50955d5286ca..9d256559bab9d 100644 --- a/src/backend/storage/buffer/buf_table.c +++ b/src/backend/storage/buffer/buf_table.c @@ -62,7 +62,7 @@ InitBufTable(int size) SharedBufHash = ShmemInitHash("Shared Buffer Lookup Table", size, size, &info, - HASH_ELEM | HASH_BLOBS | HASH_PARTITION); + HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE); } /* From 45f50c995fb658bee11bf26b8c6f4983eaa223c0 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 18 Sep 2025 11:09:15 +0900 Subject: [PATCH 95/96] pg_restore: Fix security label handling with --no-publications/subscriptions. Previously, pg_restore did not skip security labels on publications or subscriptions even when --no-publications or --no-subscriptions was specified. As a result, it could issue SECURITY LABEL commands for objects that were never created, causing those commands to fail. This commit fixes the issue by ensuring that security labels on publications and subscriptions are also skipped when the corresponding options are used. Backpatch to all supported versions. Author: Jian He Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/CACJufxHCt00pR9h51AVu6+yPD5J7JQn=7dQXxqacj0XyDhc-fA@mail.gmail.com Backpatch-through: 13 --- src/bin/pg_dump/pg_backup_archiver.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 93814152a5fd0..59eaecb4ed71e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3081,6 +3081,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; From 2e66cae935c2e0f7ce9bab6b65ddeb7806f4de7c Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 18 Sep 2025 11:22:21 +0900 Subject: [PATCH 96/96] pgbench: Remove unused argument from create_sql_command(). Author: Yugo Nagata Reviewed-by: Steven Niu Discussion: https://postgr.es/m/20250917112814.096f660ea4c3c64630475e62@sraoss.co.jp --- src/bin/pgbench/pgbench.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 125f3c7bbbe5b..3cafd88ac53b5 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -5599,7 +5599,7 @@ skip_sql_comments(char *sql_command) * struct. */ static Command * -create_sql_command(PQExpBuffer buf, const char *source) +create_sql_command(PQExpBuffer buf) { Command *my_command; char *p = skip_sql_comments(buf->data); @@ -5992,7 +5992,7 @@ ParseScript(const char *script, const char *desc, int weight) sr = psql_scan(sstate, &line_buf, &prompt); /* If we collected a new SQL command, process that */ - command = create_sql_command(&line_buf, desc); + command = create_sql_command(&line_buf); /* store new command */ if (command)