=== Applying patches on top of PostgreSQL commit ID 90893c7b7cf355b87f403a8a56293abe33ad7848 === /etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf. Mon Oct 5 16:27:36 UTC 2026 On branch cf/6763 nothing to commit, working tree clean === using 'git am' to apply patch ./v4-0001-pgstat-Allow-pg_stat_force_next_flush-to-work-in-.patch === Applying: pgstat: Allow pg_stat_force_next_flush() to work in-transaction Using index info to reconstruct a base tree... M src/backend/utils/activity/pgstat.c M src/backend/utils/activity/pgstat_relation.c M src/test/regress/expected/stats.out M src/test/regress/sql/stats.sql Falling back to patching base and 3-way merge... Auto-merging src/test/regress/sql/stats.sql Auto-merging src/test/regress/expected/stats.out Auto-merging src/backend/utils/activity/pgstat_relation.c CONFLICT (content): Merge conflict in src/backend/utils/activity/pgstat_relation.c Auto-merging src/backend/utils/activity/pgstat.c error: Failed to merge in the changes. hint: Use 'git am --show-current-patch=diff' to see the failed patch Patch failed at 0001 pgstat: Allow pg_stat_force_next_flush() to work in-transaction When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". === using patch(1) to apply patch ./v4-0001-pgstat-Allow-pg_stat_force_next_flush-to-work-in-.patch === patching file src/backend/utils/activity/pgstat.c Hunk #1 succeeded at 745 (offset 16 lines). Hunk #2 succeeded at 830 (offset 16 lines). Hunk #3 succeeded at 858 (offset 16 lines). Hunk #4 succeeded at 1448 (offset 31 lines). patching file src/backend/utils/activity/pgstat_relation.c Hunk #1 FAILED at 828. Hunk #2 FAILED at 893. 2 out of 2 hunks FAILED -- saving rejects to file src/backend/utils/activity/pgstat_relation.c.rej patching file src/test/regress/expected/stats.out Hunk #1 succeeded at 777 (offset 102 lines). Hunk #2 succeeded at 2148 (offset 114 lines). patching file src/test/regress/sql/stats.sql Hunk #1 succeeded at 362 (offset 53 lines). Hunk #2 succeeded at 1069 (offset 62 lines). Unstaged changes after reset: M src/backend/utils/activity/pgstat.c M src/test/regress/expected/stats.out M src/test/regress/sql/stats.sql Removing src/backend/utils/activity/pgstat_relation.c.rej === using 'git apply' to apply patch ./v4-0001-pgstat-Allow-pg_stat_force_next_flush-to-work-in-.patch === Applied patch to 'src/backend/utils/activity/pgstat.c' cleanly. Applied patch to 'src/backend/utils/activity/pgstat_relation.c' with conflicts. Applied patch to 'src/test/regress/expected/stats.out' cleanly. Applied patch to 'src/test/regress/sql/stats.sql' cleanly. U src/backend/utils/activity/pgstat_relation.c diff --cc src/backend/utils/activity/pgstat_relation.c index 5c70543ba88,32ee6731eb7..00000000000 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@@ -898,84 -827,110 +898,166 @@@ pgstat_relation_flush_cb(PgStat_EntryRe shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats; /* ++<<<<<<< ours + * Ignore entries that didn't accumulate any actual counts. If the table + * was truncated, we still need to flush the entry to reset the live/dead + * counters and ins_since_vacuum even when no other counts were + * accumulated. + */ + if (!lstats->tab.truncdropped && + pg_memory_is_all_zeros(&lstats->tab.counts, + sizeof(struct PgStat_TableCounts)) && + pg_memory_is_all_zeros(&lstats->tab.counts_xact, + sizeof(struct PgStat_TableCountsXact))) + return true; ++======= + * Ignore entries that didn't accumulate any actual counts, such as + * indexes that were opened by the planner but not used. With + * in-transaction flushing an entry may be flushed multiple times, so keep + * it pending if it has active transaction state and commit will merge + * counters into it. + */ + if (pg_memory_is_all_zeros(&lstats->counts, + sizeof(struct PgStat_TableCounts))) + return (lstats->trans == NULL); ++>>>>>>> theirs if (!pgstat_lock_entry(entry_ref, nowait)) return false; - /* add the values to the shared entry. */ + /* Flush non-transactional counters. */ tabentry = &shtabstats->stats; - tabentry->numscans += lstats->counts.numscans; - if (lstats->counts.numscans) + tabentry->numscans += lstats->tab.counts.numscans; + if (lstats->tab.counts.numscans) { - TimestampTz t = GetCurrentTransactionStopTimestamp(); + TimestampTz t = IsTransactionOrTransactionBlock() ? + GetCurrentStatementStartTimestamp() : + GetCurrentTransactionStopTimestamp(); if (t > tabentry->lastscan) tabentry->lastscan = t; } ++<<<<<<< ours + tabentry->tuples_returned += lstats->tab.counts.tuples_returned; + tabentry->tuples_fetched += lstats->tab.counts.tuples_fetched; + tabentry->tuples_inserted += lstats->tab.counts_xact.tuples_inserted; + tabentry->tuples_updated += lstats->tab.counts_xact.tuples_updated; + tabentry->tuples_deleted += lstats->tab.counts_xact.tuples_deleted; + tabentry->tuples_hot_updated += lstats->tab.counts_xact.tuples_hot_updated; + tabentry->tuples_newpage_updated += lstats->tab.counts_xact.tuples_newpage_updated; ++======= + tabentry->tuples_returned += lstats->counts.tuples_returned; + tabentry->tuples_fetched += lstats->counts.tuples_fetched; + tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated; + tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated; + tabentry->blocks_fetched += lstats->counts.blocks_fetched; + tabentry->blocks_hit += lstats->counts.blocks_hit; ++>>>>>>> theirs /* - * If table was truncated/dropped, first reset the live/dead counters. + * Flush transactional counters only when this relation has no active + * transaction state (i.e. no pending DML whose outcome depends on + * commit/abort). */ ++<<<<<<< ours + if (lstats->tab.truncdropped) ++======= + if (lstats->trans == NULL) ++>>>>>>> theirs { - tabentry->live_tuples = 0; - tabentry->dead_tuples = 0; - tabentry->ins_since_vacuum = 0; - } + tabentry->tuples_inserted += lstats->counts.tuples_inserted; + tabentry->tuples_updated += lstats->counts.tuples_updated; + tabentry->tuples_deleted += lstats->counts.tuples_deleted; ++<<<<<<< ours + tabentry->live_tuples += lstats->tab.counts_xact.delta_live_tuples; + tabentry->dead_tuples += lstats->tab.counts_xact.delta_dead_tuples; + tabentry->mod_since_analyze += lstats->tab.counts_xact.changed_tuples; + + /* + * Using tuples_inserted to update ins_since_vacuum does mean that we'll + * track aborted inserts too. This isn't ideal, but otherwise probably + * not worth adding an extra field for. It may just amount to autovacuums + * triggering for inserts more often than they maybe should, which is + * probably not going to be common enough to be too concerned about here. + */ + tabentry->ins_since_vacuum += lstats->tab.counts_xact.tuples_inserted; + + tabentry->blocks_fetched += lstats->tab.counts.blocks_fetched; + tabentry->blocks_hit += lstats->tab.counts.blocks_hit; ++======= + /* + * If table was truncated/dropped, first reset the live/dead counters. + */ + if (lstats->counts.truncdropped) + { + tabentry->live_tuples = 0; + tabentry->dead_tuples = 0; + tabentry->ins_since_vacuum = 0; + } + + tabentry->live_tuples += lstats->counts.delta_live_tuples; + tabentry->dead_tuples += lstats->counts.delta_dead_tuples; + tabentry->mod_since_analyze += lstats->counts.changed_tuples; + + /* + * Using tuples_inserted to update ins_since_vacuum does mean that + * we'll track aborted inserts too. This isn't ideal, but otherwise + * probably not worth adding an extra field for. It may just amount + * to autovacuums triggering for inserts more often than they maybe + * should, which is probably not going to be common enough to be too + * concerned about here. + */ + tabentry->ins_since_vacuum += lstats->counts.tuples_inserted; ++>>>>>>> theirs - /* Clamp live_tuples in case of negative delta_live_tuples */ - tabentry->live_tuples = Max(tabentry->live_tuples, 0); - /* Likewise for dead_tuples */ - tabentry->dead_tuples = Max(tabentry->dead_tuples, 0); + /* Clamp live_tuples in case of negative delta_live_tuples */ + tabentry->live_tuples = Max(tabentry->live_tuples, 0); + /* Likewise for dead_tuples */ + tabentry->dead_tuples = Max(tabentry->dead_tuples, 0); + } pgstat_unlock_entry(entry_ref); /* The entry was successfully flushed, add the same to database stats */ dbentry = pgstat_prep_database_pending(dboid); ++<<<<<<< ours + dbentry->tuples_returned += lstats->tab.counts.tuples_returned; + dbentry->tuples_fetched += lstats->tab.counts.tuples_fetched; + dbentry->tuples_inserted += lstats->tab.counts_xact.tuples_inserted; + dbentry->tuples_updated += lstats->tab.counts_xact.tuples_updated; + dbentry->tuples_deleted += lstats->tab.counts_xact.tuples_deleted; + dbentry->blocks_fetched += lstats->tab.counts.blocks_fetched; + dbentry->blocks_hit += lstats->tab.counts.blocks_hit; ++======= + dbentry->tuples_returned += lstats->counts.tuples_returned; + dbentry->tuples_fetched += lstats->counts.tuples_fetched; + dbentry->blocks_fetched += lstats->counts.blocks_fetched; + dbentry->blocks_hit += lstats->counts.blocks_hit; ++>>>>>>> theirs + + if (lstats->trans == NULL) + { + dbentry->tuples_inserted += lstats->counts.tuples_inserted; + dbentry->tuples_updated += lstats->counts.tuples_updated; + dbentry->tuples_deleted += lstats->counts.tuples_deleted; + return true; + } - return true; + /* + * This is a partial, in-transaction flush. Zero out the counters we + * already flushed so they aren't double-counted on the next flush. + */ + lstats->counts.numscans = 0; + lstats->counts.tuples_returned = 0; + lstats->counts.tuples_fetched = 0; + lstats->counts.tuples_hot_updated = 0; + lstats->counts.tuples_newpage_updated = 0; + lstats->counts.blocks_fetched = 0; + lstats->counts.blocks_hit = 0; + + return false; } void