Skip to content

cf 5556 - #16

Draft
gburd wants to merge 23 commits into
masterfrom
cf-5556
Draft

gburd wants to merge 23 commits into
masterfrom
cf-5556

Conversation

@gburd

@gburd gburd commented Oct 24, 2025

Copy link
Copy Markdown
Owner

No description provided.

@gburd
gburd force-pushed the cf-5556 branch 7 times, most recently from e777a6e to 650f621 Compare November 1, 2025 17:22
@gburd
gburd force-pushed the cf-5556 branch 6 times, most recently from 16e0007 to 331cd76 Compare November 7, 2025 20:56
@gburd
gburd force-pushed the cf-5556 branch 4 times, most recently from 9558f42 to 05c4e60 Compare November 16, 2025 18:53
@gburd
gburd force-pushed the cf-5556 branch 4 times, most recently from ae8af13 to 9f584af Compare November 19, 2025 18:18
@gburd
gburd force-pushed the cf-5556 branch 2 times, most recently from b142c27 to 94e88c7 Compare December 1, 2025 18:10
@gburd
gburd force-pushed the cf-5556 branch 2 times, most recently from 0d87b2f to d4f607c Compare March 10, 2026 18:18
@gburd
gburd force-pushed the master branch 3 times, most recently from 571be3b to 03facc1 Compare March 10, 2026 18:37

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OCR found 27 issue(s).

  • 25 inline, 2 in summary (inline capped at 25)
  • ⚠️ 1 warning(s) during review

📄 src/tools/pgindent/pgindent (L1-L1)

This shebang change is unrelated to the feature and breaks the tree-wide convention: every other Perl script under src/ uses #!/usr/bin/perl (e.g. genbki.pl, gen_node_support.pl, mark_pgdllimport.pl, copyright.pl); there are zero #!/usr/bin/env perl shebangs in the tree. This is a local developer-environment convenience (consistent with the Nix files added in this PR) that will be rejected on -hackers as unrelated churn. Revert to keep the diff minimal.

💡 Suggested change

Before:

#!/usr/bin/env perl

After:

#!/usr/bin/perl

📄 src/backend/commands/copyto.c (L881-L883)

Minor: this path does two separate syscache lookups per child -- get_rel_relkind(child) at the top of the loop and now get_rel_persistence(child) here, plus get_rel_name(child) again in each error branch. Since the relcache entry is being looked up repeatedly for the same OID, consider fetching the tuple once (e.g. via SearchSysCache1(RELOID)) and reading relkind/relpersistence/relname from it, consistent with how hot loops over inheritors avoid redundant lsyscache calls. Low severity / maintainability only.

Comment on lines +28 to +29
jobs:
review:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caller invokes a reusable workflow that authenticates to AWS Bedrock via GitHub OIDC (per the header comment: "Auth is GitHub OIDC"). OIDC token minting requires id-token: write, which is never granted by default and cannot be requested inside the reusable workflow -- it must be declared by the caller. Without a permissions: block granting id-token: write, the aws-actions/configure-aws-credentials call in the reusable workflow will fail with "Credentials could not be loaded" / missing OIDC token. The review workflow also needs pull-requests: write (and likely issues: write) to post review comments. Add an explicit least-privilege permissions: block at the workflow level.

Suggested change
jobs:
review:
permissions:
contents: read
id-token: write
pull-requests: write
issues: write
jobs:
review:

(startsWith(github.event.comment.body, '/open-code-review') ||
startsWith(github.event.comment.body, '@open-code-review') ||
startsWith(github.event.comment.body, '/pg-history')))
uses: gburd/ci-workflows/.github/workflows/ocr-review.yml@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The external reusable workflow is pinned to the mutable tag @v1. A tag can be force-moved in the gburd/ci-workflows repo, so a compromise of that repo (or a maintainer) would let attacker-controlled logic run with this fork's AWS OIDC role and GITHUB_TOKEN on every PR. Pin third-party/external reusable workflows to a full commit SHA (optionally with a # v1.x trailing comment) rather than a tag.

Suggested change
uses: gburd/ci-workflows/.github/workflows/ocr-review.yml@v1
uses: gburd/ci-workflows/.github/workflows/ocr-review.yml@<full-commit-sha> # v1


on:
pull_request:
# Note: no draft filter — drafts are reviewed too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-ASCII em-dash (U+2014) in this comment. PostgreSQL requires ASCII-only in source and diffs (no em-dashes, smart quotes, or ellipses). Replace with a plain hyphen or --.

Suggested change
# Note: no draft filter — drafts are reviewed too.
# Note: no draft filter -- drafts are reviewed too.

Comment on lines +12 to +14
jobs:
check:
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caller lacks a permissions: block. The reusable workflow authenticates to Bedrock via OIDC (needs id-token: write, never granted by default and only requestable from the caller) and, per the header comment, "opens/updates one issue here" (needs issues: write). Without these, OIDC auth and/or issue creation will fail. Add an explicit least-privilege permissions: block.

Suggested change
jobs:
check:
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1
permissions:
contents: read
id-token: write
issues: write
jobs:
check:
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1


jobs:
check:
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External reusable workflow pinned to the mutable tag @v1. Pin to a full commit SHA so a force-moved tag in gburd/ci-workflows cannot silently change the code that runs with this repo's OIDC/token access.

Suggested change
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@<full-commit-sha> # v1

Comment on lines +274 to +276
ereport(ERROR,
errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not find an unpinned shared buffer"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error-message rewording (and the matching one in localbuf.c) is unrelated to the modified_idx_attrs / HOT table-AM refactor that this change set is about. Mixing an independent buffer-exhaustion message/errcode change into this patch violates the "minimal diff, one logical change per commit" discipline and will draw a split-it request on -hackers. Recommend moving these two hunks to a separate patch.

Confidence: high.

ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("no empty local buffer available")));
errmsg("could not find an unpinned local buffer")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message rewording is unrelated to the table-AM/HOT refactor that is the purpose of this change set. Like the freelist.c hunk, it should be split into a separate, self-contained patch rather than bundled here (patch hygiene: one logical change per commit).

Confidence: high.

Comment on lines +881 to +883
/* Unlogged partitions are inaccessible during recovery. */
if (RecoveryInProgress() &&
get_rel_persistence(child) == RELPERSISTENCE_UNLOGGED)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This COPY-TO-during-recovery guard for unlogged (partitioned) tables is unrelated to the HOT-update / modified_idx_attrs work in the other file of this change (repack.c). Bundling two independent concerns in one patch is a common pgsql-hackers rejection reason; split it into its own commit/patch with its own rationale and thread reference. (moderate confidence)

Comment on lines +887 to +891
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery"),
errdetail("Partition \"%s\" is an unlogged table in partitioned table \"%s\".",
relation_name, RelationGetRelationName(rel)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check runs inside the foreach_oid loop before the "Exclude tables with no data" step, so it also fires on intermediate partitioned tables (RELKIND_HAS_PARTITIONS), which are themselves persistence UNLOGGED-capable but hold no data. For a permanent root with an unlogged intermediate partitioned level, this errors with errdetail "...is an unlogged table..." even though that relation stores no rows. Consider only checking leaf relations (e.g. test relkind / move the check after the no-data exclusion) so the detail message isn't misleading. (low confidence)


cmdline2 = psprintf("exec %s", cmdline);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This execl -> execlp change is unrelated to the HOT-update / SupportRequestSubattr feature this PR implements, and it changes behavior in a risky way. shellprog comes from SHELLPROG, defined as $(SHELL) (Make) or /bin/sh (meson) -- normally an absolute path. execl() runs exactly that file; execlp() performs a $PATH search when the argument has no slash. The replacement only alters behavior if shellprog has no slash, in which case the shell is resolved from the environment's PATH -- a PATH-injection footgun that did not exist before. This looks like a local Nix/direnv convenience (matching the flake.nix/shell.nix/.envrc files added elsewhere in this PR). Revert it to keep the diff minimal and avoid the behavior change.

Suggested change
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

- Rebased on upstream 5ffb8c7.
- 0006: the narrowing no longer compares a column an index stores
  again: it is changed exactly when it is still in the changed set
  (IndexDatumsDiffer() takes the set), so its values are not fetched a
  second time.  Only expression datums are compared again.
- hot_updates: a write-back through an expression that returns the
  value as is, evaluated for a predicate column's change (HOT after
  0006).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OCR found 21 issue(s).

  • 21 inline, 0 in summary

Comment thread .clangd
Comment on lines +1 to +2
Diagnostics:
MissingIncludes: None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file (along with .envrc, .gdbinit, .local-gitignore, .mcp.json, flake.nix, shell.nix, and pg-aliases.sh) is personal developer tooling and does not belong in a PostgreSQL contribution destined for pgsql-hackers/commitfest. It violates the "minimal diff" discipline: a patch must not bundle unrelated scaffolding at the repo root. The repo already uses per-directory .gitignore files, and this change even ships its own .local-gitignore mechanism (git config core.excludesFile .local-gitignore) -- use that to keep these files local and untracked rather than committing them. Including them will draw immediate rejection on the list.

Comment thread .gdbinit
Comment on lines +19 to +20
# Main entry: heap_update
break heapam.c:3210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These file+line breakpoints are already stale against the code in this same change. For example break heapam.c:3210 is annotated "Main entry: heap_update", but heap_update()'s definition is at ~line 3278 in the modified file; the "HOT decision block" at 4019/4024/4033 no longer matches -- the HOT decision is now if (hot_allowed) at ~line 4174. Likewise break execIndexing.c:370 is annotated "Index skip decision" but line 370 is return true; inside the predicate-qual helper, not any skip decision. Line-number breakpoints are inherently fragile: they silently point at the wrong code as the patch evolves and will mis-direct any debugging session. If this file were to be kept at all (it should not be committed -- see .clangd), breakpoints should be keyed to function names, not line numbers.

Comment thread .mcp.json
@@ -0,0 +1,15 @@
{
"mcpServers": {
"postgresq": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server key "postgresq" is missing a trailing 'l' and looks like a typo for "postgresql". The same truncated name is referenced from .envrc via use project_mcp postgresq, so the two are at least internally consistent, but the identifier itself is almost certainly wrong. If intentional, it is highly confusing; if a typo, it names the MCP server incorrectly.

Comment thread .envrc
Comment on lines +4 to +10
source_up
watch_file flake.nix
use flake
use project_steering c aws
use project_steering postgresql
use project_mcp postgresq
use project_mcp context7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_up relies on a parent-directory .envrc (the comment points at ~/ws/postgres/.envrc), and the custom use project_steering / use project_mcp directives depend on direnv functions and MCP servers that exist only on one developer's machine. Combined with the hard-coded host paths in shell.nix/pg-aliases.sh (e.g. /tmp/test-db-$(basename $PWD), sudo tee /proc/sys/kernel/core_pattern), this ties the committed tree to a single developer's layout and is not portable. This file should not be committed -- keep it local via the .local-gitignore mechanism.

Comment on lines +35 to +38
(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
(startsWith(github.event.comment.body, '/open-code-review') ||
startsWith(github.event.comment.body, '@open-code-review') ||
startsWith(github.event.comment.body, '/pg-history')))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue_comment trigger fires for any comment starting with /open-code-review, @open-code-review, or /pg-history, but there is no gate on the comment author. Any user who can comment on a PR (not only maintainers) can start an AI review and drive Bedrock spend. Add an author_association gate on the if: condition, e.g. require the commenter to be OWNER/MEMBER/COLLABORATOR:

(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
(startsWith(...)))

If gating is intended to live downstream in gburd/ci-workflows, note that the reusable workflow is not vendored in this repo, so it cannot be verified here. Confidence: high.

Comment on lines +349 to +353
/* Temporary and unlogged relations are inaccessible during recovery. */
if (!RelationIsPermanent(rel) && RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new user-visible error path (currtid/currtid2 on a standby) has no test coverage. The accompanying TAP additions in src/test/recovery/t/001_stream_rep.pl only exercise the COPY TO guard in copyto.c, not this one. Per project policy a user-visible behavior change should include a test covering the error path, e.g. a currtid2(...) call against an unlogged/temp relation on $node_standby_1 asserting the same qr/cannot access temporary or unlogged relations during recovery/ message. Confidence: moderate.


cmdline2 = psprintf("exec %s", cmdline);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This execl -> execlp change is unrelated to the SUBATTR support-function feature that the rest of this patch implements; it is unrelated churn and should be split out (or dropped).

Functionally it is also a footgun. shellprog comes from SHELLPROG, which is defined as $(SHELL) in src/test/regress/GNUmakefile and hardcoded /bin/sh in meson.build -- i.e. normally an absolute path. For an absolute path execlp is identical to execl, so this buys nothing. The only case it changes behavior is when $(SHELL) is a bare command name with no slash (e.g. make SHELL=sh), and then execlp resolves the shell through the ambient $PATH instead of failing. That is a non-deterministic, PATH-injection hazard in the test harness for no benefit. Revert to execl.

Confidence: high.

Suggested change
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

@@ -1,4 +1,4 @@
#!/usr/bin/perl
#!/usr/bin/env perl

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shebang change is unrelated to the rest of the patch and breaks the tree-wide convention. Every other Perl script in the PostgreSQL source (e.g. genbki.pl, gen_node_support.pl, copyright.pl, mark_pgdllimport.pl, and ~30 others) uses the fixed #!/usr/bin/perl shebang. Switching only pgindent to #!/usr/bin/env perl makes it the sole outlier and is gratuitous churn that will draw a rejection on -hackers. If a case is to be made for /usr/bin/env perl, it should be a separate, deliberate, tree-wide patch discussed on its own; it does not belong bundled into this change. Revert this line.

Suggested change
#!/usr/bin/env perl
#!/usr/bin/perl

Comment on lines +350 to +353
if (!RelationIsPermanent(rel) && RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit (low confidence): this new ereport uses the legacy extra-parentheses wrapping ereport(ERROR, (errcode(...), errmsg(...))), but every other ereport in this same function (the RELKIND_HAS_STORAGE check just above, and the two in currtid_for_view) uses the modern parenthesis-free form. For a minimal patch that reads as if always written that way, match the surrounding style.

Suggested change
if (!RelationIsPermanent(rel) && RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
if (!RelationIsPermanent(rel) && RecoveryInProgress())
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery"));

Comment on lines +260 to +262
UPDATE subattr_two SET d = jsonb_set(d, '{c}', '0');
-- both rows HOT: the index's value didn't change
SELECT pg_stat_get_xact_tuples_hot_updated('subattr_two'::regclass) AS hot;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test's HOT-count assertions do not actually exercise the SupportRequestSubattr path in a way that a regression could be caught in normal (non-assert) builds. IndexUnaffected() in nodeModifyTable.c, when the subattr proof does not apply, falls back to IndexEntryChanges(), which evaluates the expression index and compares its datums byte-for-byte. For both UPDATEs here the fallback reaches the same HOT classification as the proof path:

  • jsonb_set(d,'{c}','0'): two_keys(d,'a','b') is unchanged, so the fallback also treats the index as unaffected -> HOT.
  • jsonb_set(d,'{b}','0'): the value changes, so both paths -> not HOT.

So pg_stat_get_xact_tuples_hot_updated() returns 2 / 2 with the support function reverted too; the EXPLAIN/index-scan check for '2/0' likewise passes via the fallback. Per the test-discipline rule (a test that still passes with the feature reverted is worthless), the happy-path assertions here do not catch a regression in the support-function path on a non-assert buildfarm animal. The comment itself concedes the real verification only happens under USE_ASSERT_CHECKING / amcheck. Consider either dropping the redundant HOT-count assertions or adding coverage that is observably different only when the support function is consulted. (moderate confidence)

…ap_update compares slots

- 0008: hashcomparedatums() says "different" without hashing when the
  type's default btree opclass has equalimage true for the index's
  collation; the executor only asks about values that differ once
  detoasted.  Exclusion-constraint collision tests use interval spans of
  1 and 2^32 microseconds (int8 is no longer asked).
- 0002: simple_heap_update() puts the new tuple in a slot and uses
  table_slot_modified_idx_attrs(); HeapUpdateModifiedIdxAttrs() is now
  assert-only (heap_update()'s cross-check).
- Rebased on upstream.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OCR found 26 issue(s).

  • 25 inline, 1 in summary (inline capped at 25)

📄 src/tools/pgindent/pgindent (L1-L1)

This change breaks the tree-wide convention: every other Perl script in the PostgreSQL source tree (genbki.pl, gen_node_support.pl, copyright.pl, mark_pgdllimport.pl, and ~30 others) uses #!/usr/bin/perl. After this edit, pgindent would be the sole outlier using #!/usr/bin/env perl.

The change has no stated functional purpose and is unrelated to any other work in this patch set, so it is unnecessary churn that will draw a rejection on -hackers. If a env-based shebang were genuinely desired, it would need to be a deliberate, tree-wide decision discussed on the list, not a one-off edit to a single file. Revert this to #!/usr/bin/perl to keep the diff minimal and consistent.

💡 Suggested change

Before:

+#!/usr/bin/env perl

After:

+#!/usr/bin/perl

Comment thread .gdbinit
Comment on lines +19 to +20
# Main entry: heap_update
break heapam.c:3210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file (and the whole .clangd/.envrc/.gdbinit/.local-gitignore/.mcp.json/flake.nix/shell.nix/pg-aliases.sh group) is developer-local environment scaffolding with no relationship to the stated code change. It is unrelated churn that violates PostgreSQL patch hygiene (minimal diff) and would be rejected on pgsql-hackers; it should not be committed to the repository. Beyond that, the hardcoded source line numbers here are already wrong: heapam.c:3210 lands inside heap_delete's cleanup (not heap_update), and heapam.c:4019/:4024/:4033 land inside heap_lock_tuple's visibility-map/WAL code, not the described HOT decision block. Line-number breakpoints will keep silently drifting, making this actively misleading.

Comment thread .gdbinit
break heap_hot_indexed_tuple_size

# Create augmented tuple with embedded modified-column bitmap
break heap_hot_indexed_create_tuple

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These break targets reference functions that do not exist in the source tree (e.g. heap_hot_indexed_create_tuple, heap_hot_indexed_serialize_bitmap, heap_hot_indexed_read_bitmap). The only occurrence of these symbols in the repo is this .gdbinit file itself. The breakpoints will never resolve, confirming this script is stale, personal tooling that does not belong in the patch.

Comment thread .mcp.json
@@ -0,0 +1,15 @@
{
"mcpServers": {
"postgresq": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON key "postgresq" is misspelled (missing trailing l); it should be "postgresql". The same truncated name appears in .envrc (use project_mcp postgresq), so the two are self-consistent but the name is almost certainly unintended.

Suggested change
"postgresq": {
"postgresql": {

Comment thread .envrc
# Inherit shared CFLAGS from ~/ws/postgres/.envrc, load this worktree's
# flake, then layer the postgres agent context (helpers need flake's PATH +
# write into this worktree's dir).
source_up

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on local-machine assumptions that are not reproducible for anyone else: source_up reads a parent ~/ws/postgres/.envrc that is not part of the repo, and use project_steering / use project_mcp are custom direnv functions defined nowhere in this change. Running direnv allow on a fresh checkout will error. This is personal tooling, not shareable repo config, and should not be committed.

Comment thread .envrc
use flake
use project_steering c aws
use project_steering postgresql
use project_mcp postgresq

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

project_mcp postgresq carries the same truncated name as the .mcp.json key; if it is meant to reference the MCP server defined there, both should read postgresql.

Comment on lines +458 to +461
while (isspace((unsigned char) *p))
p++;
if (*p == '-' || *p == '+')
p++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key_may_be_position() uses isspace() from <ctype.h> with the current locale. The leading comment claims answering "yes too often" is the safe direction (an over-reported overlap only costs an unnecessary index entry), which is true for soundness. But locale-dependent isspace() is a portability smell in the backend: on some locales/platforms it may classify bytes >0x7F as space, and the behavior differs from strtol()'s skip set in subtle ways across platforms. Prefer scanner_isspace()/the backend's locale-independent helpers to keep the heuristic deterministic across the supported platforms. (low confidence that this causes a bug, but it is the kind of locale dependence the tree avoids in parse/scan paths.)

Comment on lines +94 to +97
WITH current_tuple AS (SELECT ctid FROM hot_test WHERE id = 1)
SELECT has_hot_chain('hot_test', current_tuple.ctid) AS has_chain,
chain_position, print_hot_chain.ctid, lp_flags, t_ctid
FROM current_tuple, LATERAL print_hot_chain('hot_test', current_tuple.ctid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected output hardcodes concrete ctids ((0,1), (0,4)) that depend on exact tuple placement within the page. With fillfactor=50 and these fixed small tuples this should be deterministic, but it couples the test to heap layout details (tuple alignment/padding). If future changes to tuple header size or alignment shift placement, the test breaks in a way unrelated to HOT behavior. Consider asserting the structural property (chain length / has_hot_chain result) rather than literal ctids where possible. (low confidence; acceptable as-is but worth noting.)

Comment on lines +4162 to +4163
/* We fill in schema names below, so work on a copy */
schemaElts = copyObject(schemaElts);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new copyObject deep-copies the entire schemaElts list, but the function's header comment (lines 4150-4152) still reads "we copy any CREATE TABLE subcommands that we might modify" and transformCreateSchemaCreateTable still does a redundant flat-copy of each CreateStmt to "not damage the input data structure." The header comment is now stale/misleading: the whole list is copied up front, not just CREATE TABLE subcommands. Please update the header comment to reflect that the entire list is copied so a reader isn't misled. (moderate confidence)

* analyzes the elements of a CREATE SCHEMA statement
*
* This presently has two responsibilities. We verify that no subcommands
* This presently has two responsibilities. We ensure that no subcommands

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reword ("verify" -> "ensure") is unneeded churn and is slightly less precise: this first responsibility is still purely a verification (it raises an error on a schema-name mismatch); the new fill-in behavior falls under the schema-name handling described below. Per the minimal-diff discipline, prefer leaving the untouched wording as-is unless the change is required. (low confidence)

Suggested change
* This presently has two responsibilities. We ensure that no subcommands
* This presently has two responsibilities. We verify that no subcommands


cmdline2 = psprintf("exec %s", cmdline);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This execl -> execlp change is unrelated to the SupportRequestSubattr feature this patch adds and looks like scope creep. SHELLPROG is $(SHELL) from the Makefile and "/bin/sh" under meson; both are absolute paths, for which execlp behaves identically to execl (PATH is only searched when the name has no slash). So this is a behavioral no-op in the normal case and a silent PATH-search footgun in the unusual case where SHELLPROG is a bare name. Either drop this hunk from the feature patch (minimal-diff discipline), or split it into its own commit with a justification for why the test shell should be resolved via PATH. Confidence: high.

Suggested change
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OCR found 23 issue(s).

  • 23 inline, 0 in summary
  • ⚠️ 1 warning(s) during review

Comment on lines +10 to +11
on:
pull_request:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No top-level permissions block is declared. A caller workflow that invokes a reusable workflow via uses: can declare top-level permissions, and those are passed down to constrain the reusable workflow's GITHUB_TOKEN. Without it, this job inherits the repo/org default token scopes (often broad write), while it is triggered by pull_request and issue_comment on PR content. Declare least-privilege here (the sibling pg-ci.yml sets permissions: contents: read). The AI review likely needs pull-requests: write to post comments; grant exactly that and nothing more.

Note: permissions and timeout-minutes cannot be set on a reusable-workflow uses: job itself, so the per-job timeout to bound Bedrock spend must be enforced inside gburd/ci-workflows/.../ocr-review.yml (not verifiable from this repo). (high confidence on the missing top-level permissions)

Suggested change
on:
pull_request:
permissions:
contents: read
pull-requests: write
on:
pull_request:

(startsWith(github.event.comment.body, '/open-code-review') ||
startsWith(github.event.comment.body, '@open-code-review') ||
startsWith(github.event.comment.body, '/pg-history')))
uses: gburd/ci-workflows/.github/workflows/ocr-review.yml@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cross-repo reusable workflow is pinned to the mutable major tag @v1. Tags can be moved or hijacked; if the upstream repo or tag is compromised, this workflow runs attacker-controlled logic with whatever token scopes it gets, on every PR. For a third-party reusable workflow, pin to a full commit SHA (optionally with # v1 trailing comment) and bump deliberately. (high confidence)


on:
pull_request:
# Note: no draft filter — drafts are reviewed too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-ASCII em-dash in this comment. The PostgreSQL source standard is ASCII-only in source and diffs (no smart quotes, em-dashes, or ellipses). Replace the em-dash with a regular hyphen/dashes. (high confidence)

Suggested change
# Note: no draft filter — drafts are reviewed too.
# Note: no draft filter -- drafts are reviewed too.


jobs:
check:
uses: gburd/ci-workflows/.github/workflows/ocr-model-check.yml@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same mutable-tag concern as the review workflow: the cross-repo reusable workflow is pinned to @v1. Pin to a full commit SHA to prevent a moved/hijacked tag from silently changing the executed logic. Also note no top-level permissions is declared; this notify-only job opens/updates an issue, so it needs issues: write and nothing else -- declare it explicitly rather than inheriting broad defaults. (high confidence)

Comment on lines +23 to +25
sync:
runs-on: ubuntu-latest
steps:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job runs unattended on a 3-hourly timer, performs git rebase against upstream, and then git push --force-with-lease. If the rebase hits a conflict, git rebase exits non-zero and (under set -euo pipefail) the step fails before the push -- good. But the repo is left in a half-rebased state only on the ephemeral runner, so that is harmless. The real reliability gap is the missing timeout-minutes: a stuck git fetch/rebase (e.g. network stall) can hang the runner indefinitely. Add a job-level timeout-minutes bound. (moderate confidence)

Suggested change
sync:
runs-on: ubuntu-latest
steps:
sync:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:

Comment thread .clangd
Comment on lines +1 to +2
Diagnostics:
MissingIncludes: None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file (together with .envrc, .gdbinit, .local-gitignore, .mcp.json, flake.nix, shell.nix, pg-aliases.sh and the .github/workflows/fork-* files) is personal developer/CI scaffolding. The repo's own root .gitignore states the project policy explicitly: "Auxiliary files from local workflows, your preferred editor, etc. should be ignored locally using $GIT_DIR/info/exclude or ~/.gitexclude." Tracking these in the tree violates the minimal-diff discipline and would be an immediate rejection on pgsql-hackers. Keep them out of the patch via a personal/local ignore instead of committing them. [high confidence]

Comment thread .gdbinit
# =========================================================================

# Main entry: heap_update
break heapam.c:3210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These hardcoded source-line breakpoints are already stale against the current tree. heapam.c:3210 is the tail of heap_delete() (heap_freetuple(old_key_tuple)), not "Main entry: heap_update"; heapam.c:4019/4024/4033 land in a heap_lock_tuple WAL block, not the claimed "HOT decision" / "augmented tuple size check". Line-number breakpoints drift silently with every edit and will resolve to the wrong code. [high confidence]

Comment thread .gdbinit
break heap_hot_indexed_tuple_size

# Create augmented tuple with embedded modified-column bitmap
break heap_hot_indexed_create_tuple

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These function-name breakpoints reference symbols that do not exist in the tree (code_search for heap_hot_indexed_create_tuple, heap_hot_indexed_tuple_size, etc. returns no matches), and the cited files heapam_indexscan.c / indexam.c paths plus their line numbers come from an unmerged HOT-indexed-updates patch series. As committed this .gdbinit targets code that isn't present, so most of these breakpoints will fail to resolve. [high confidence]

Comment thread pg-aliases.sh
alias pg-full-clean='trash "$PG_BUILD_DIR" "$PG_INSTALL_DIR" 2>/dev/null || rm -rf "$PG_BUILD_DIR" "$PG_INSTALL_DIR"; echo "Build and install directories cleaned"'

# Database management
alias pg-init='trash "$PG_DATA_DIR" 2>/dev/null || rm -rf "$PG_DATA_DIR"; "$PG_INSTALL_DIR/bin/initdb" --debug --no-clean "$PG_DATA_DIR"'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These recursive deletes are guarded only by environment variables that may be empty. If pg-aliases.sh is sourced outside the Nix shell that exports PG_DATA_DIR/PG_BUILD_DIR/PG_INSTALL_DIR, the expansions collapse to e.g. rm -rf "" or operate on an unintended cwd-relative path, risking data loss. Add a non-empty guard (e.g. [ -n "$PG_DATA_DIR" ] || return 1) before any trash/rm -rf. [medium confidence]

Comment thread pg-aliases.sh

# Clean commands
alias pg-clean='ninja -C "$PG_BUILD_DIR" clean'
alias pg-full-clean='trash "$PG_BUILD_DIR" "$PG_INSTALL_DIR" 2>/dev/null || rm -rf "$PG_BUILD_DIR" "$PG_INSTALL_DIR"; echo "Build and install directories cleaned"'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same unguarded-variable hazard: if PG_BUILD_DIR/PG_INSTALL_DIR are unset, this becomes rm -rf "" "". Guard that both variables are non-empty before the recursive delete. [medium confidence]

Comment thread shell.nix
patches =
(oldAttrs.patches or [])
++ [
./glibc-no-fortify-warning.patch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patchedGlibc references ./glibc-no-fortify-warning.patch, but that patch file is not present at the repo root (file_find confirms it is missing). flake evaluation of the dev shell (nix develop) will fail with a missing-path error. Either add the patch file to the repo or drop the override. [high confidence]

Comment thread flake.nix
config.allowUnfree = true;
};

libumemPkg = libumem.packages.${system}.libumem;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pins libumem to a codeberg tag via git+https, which is only reproducible if the resolved flake.lock is committed. No flake.lock is present at the repo root (file_find /flake.lock returns not found), so the tag ref and nixpkgs revisions are unpinned and the environment can silently change if the tag is re-pointed. Commit the generated flake.lock alongside flake.nix. [high confidence]

Comment on lines +23 to +25
sync:
runs-on: ubuntu-latest
steps:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sync job has no timeout-minutes. It runs unattended on a 3-hourly timer and performs network operations (git fetch from upstream, git push). If any step hangs (e.g. network stall), the job can run until the runner's 6-hour hard limit, needlessly consuming Actions minutes. Add a job-level timeout-minutes (a few minutes is ample for a rebase/push).

Suggested change
sync:
runs-on: ubuntu-latest
steps:
sync:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:

Comment on lines +392 to +393
TM_FailureData *tmfd, LockTupleMode lockmode,
const Bitmapset *modified_idx_attrs, bool hot_allowed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exported heap_update() signature changes (LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes -> LockTupleMode lockmode, const Bitmapset *modified_idx_attrs, bool hot_allowed), and the tuple_update table-AM callback in tableam.h gains a modified_idx_attrs parameter. Both are API/ABI breaks: any out-of-tree extension calling heap_update() directly, and any third-party table AM implementing tuple_update, will fail to compile against this header and is ABI-incompatible. This is fine for a HEAD-targeted feature but must not be backpatched, and the AM-API change should be called out in the commit message / AM docs so TAM authors update their callbacks and recompute HOT/lock decisions from the new modified_idx_attrs.

Comment on lines +4162 to +4163
/* We fill in schema names below, so work on a copy */
schemaElts = copyObject(schemaElts);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header comment is now stale. With the new upfront copyObject(schemaElts), the whole element list is privately copied, so the claim "we copy any CREATE TABLE subcommands that we might modify" no longer describes the strategy (and the per-CreateStmt flat-copy in transformCreateSchemaCreateTable is now redundant). Please update this note to reflect that the entire input list is copied up front so subcommand schema names can be filled in without modifying the caller's parse tree.

Comment on lines +4130 to 4131
* This presently has two responsibilities. We ensure that no subcommands
* are trying to create objects outside the new schema. We also attempt to

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reword from "verify" to "ensure" slightly reduces accuracy for the sentence it sits in: the first responsibility described here ("no subcommands are trying to create objects outside the new schema") is still a check that errors on mismatch -- "verify" was the better verb for that clause. The actual behavioral change (now filling in the schema name when absent) is a separate responsibility not captured by this edit. Consider reverting this cosmetic reword to keep the diff minimal, or reword the paragraph to actually describe the new set-or-check behavior. Low confidence / low priority.

Comment on lines +4799 to +4800
slot = MakeTupleTableSlot(RelationGetDescr(relation), &TTSOpsBufferHeapTuple, 0);
bslot = (BufferHeapTupleTableSlot *) slot;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds, on every simple_heap_update() call, an extra buffer read (heap_fetch does ReadBuffer + share lock + unlock; heap_update then reads the same block again), two TupleTableSlot allocations, and a full tuple deform/compare via table_slot_modified_idx_attrs(). simple_heap_update() is the hot path for every catalog tuple update (CatalogTupleUpdate/WithInfo), so this is a measurable per-update regression in DDL/catalog-heavy transactions versus the old single-pass-under-one-lock design.

At minimum, short-circuit when the relation has no indexes: in that case modified_idx_attrs is always NULL, hot_allowed is always true, and none of the fetch/slot/compare work is needed. Guard with relation->rd_rel->relhasindex (or an empty INDEX_ATTR_BITMAP_INDEXED) before doing the slot/heap_fetch dance.

* (see the header comment), so both reads see the same version; if one
* happens anyway, heap_update() reports it.
*/
slot = MakeTupleTableSlot(RelationGetDescr(relation), &TTSOpsBufferHeapTuple, 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style inconsistency: the old slot is built with MakeTupleTableSlot(..., 0) while the new slot a few lines below uses MakeSingleTupleTableSlot(...). They are identical (MakeSingleTupleTableSlot is just MakeTupleTableSlot(tupdesc, ops, 0)), and both are released with ExecDropSingleTupleTableSlot. Use MakeSingleTupleTableSlot for both so the create/drop pairing reads consistently.

Suggested change
slot = MakeTupleTableSlot(RelationGetDescr(relation), &TTSOpsBufferHeapTuple, 0);
slot = MakeSingleTupleTableSlot(RelationGetDescr(relation), &TTSOpsBufferHeapTuple);

Comment on lines +274 to +276
ereport(ERROR,
errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not find an unpinned shared buffer"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style inconsistency: the surrounding buffer code (including the matching call in localbuf.c modified in this same change) uses the parenthesized ereport form ereport(ERROR, (errcode(...), errmsg(...))). For consistency within the subsystem, consider wrapping the arguments in parentheses here too. Not a correctness issue — low confidence/low priority nit.

Suggested change
ereport(ERROR,
errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not find an unpinned shared buffer"));
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not find an unpinned shared buffer")));

Comment on lines +725 to +726
* The contents of this array are mostly generated from the CLDR project's
* windowsZones.xml file. See src/timezone/README for more information.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment points to src/timezone/README, but no such file exists in the tree (there is no README under src/timezone/, nor any committed CLDR/windowsZones generator or source). A dangling doc reference like this will mislead future maintainers who try to regenerate the table. Either add the referenced README (documenting the CLDR windowsZones.xml generation process, as the comment claims) together with any generator script in the same patch, or drop/reword the reference to describe how the array is actually maintained. (high confidence)

@@ -1,4 +1,4 @@
#!/usr/bin/perl
#!/usr/bin/env perl

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shebang change makes pgindent inconsistent with the rest of the tree. Every other Perl script in the repository (e.g. src/tools/copyright.pl, src/tools/mark_pgdllimport.pl, src/backend/catalog/genbki.pl, and ~30 others) uses #!/usr/bin/perl. Changing only this one file to #!/usr/bin/env perl is an unrelated, non-minimal change that breaks that convention and does not fit the stated purpose of the surrounding patch.

If the goal is to support Perl installed outside /usr/bin (e.g. via a Nix/direnv environment, as the other added files in this change suggest), that is a tree-wide policy decision that must be proposed and discussed on pgsql-hackers and applied consistently to all scripts in one dedicated patch -- not slipped into a single file. As-is, this hunk should be dropped from the patch.


cmdline2 = psprintf("exec %s", cmdline);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing execl() to execlp() is an unexplained, unrelated change in a security-sensitive code path. With the current build definitions SHELLPROG is always an absolute path ($(SHELL) under make, hardcoded "/bin/sh" under meson), so for a name containing a slash execlp behaves identically to execl and there is no benefit. The only case it differs is a bare program name, where execlp performs a PATH search -- introducing PATH-lookup/injection surface for zero gain. Unless there is a stated reason (e.g. a planned non-absolute SHELLPROG), revert to execl to keep the diff minimal and avoid the footgun. (moderate confidence)

Suggested change
execlp(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);

@github-actions

Copy link
Copy Markdown

📊 OCR posted 22/23 inline comment(s).

1 could not be posted

src/test/regress/sql/foreign_key.sql L3002: Validation Failed: {"resource":"PullRequestReviewComment","code":"custom","field":"pull_request_review_thread.line","message":"could not be resolved"} - https://docs.github.com/rest/pulls/comments#create-a-review-comment-for-a-pull-request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant