Skip to content

Pg19 support - #8753

Open
ibrahim halatci (ihalatci) wants to merge 36 commits into
mainfrom
pg19-support
Open

Pg19 support#8753
ibrahim halatci (ihalatci) wants to merge 36 commits into
mainfrom
pg19-support

Conversation

@ihalatci

Copy link
Copy Markdown
Contributor

DESCRIPTION: Enables pg19 support for Citus

ibrahim halatci (ihalatci) and others added 27 commits June 6, 2026 15:44
… CI image bump) (#8601)

DESCRIPTION: Add PG19 build support; extension compiles and loads.

First PR of the PG19 enablement series tracked in #8597. Achieves
source-level compatibility with PG19 (`pg19devel`) and a clean
`CREATE EXTENSION citus` while keeping PG17 and PG18 builds green.
Regression-test correctness and PG19 feature integration land in
follow-up PRs against the `pg19-support` development branch.

## Commits

1. **ci** — bump `image_suffix` to `-dev-e11d99c` (PG19-capable test
   images) and PG minors to 16.14/17.10/18.4; add `pg19_version` and
   wire `19devel` into the **build matrix only**.
2. **source-level compatibility + shmem sizing** — accept PG19 in
   `configure` / `pg_version_constants.h`; centralised compat shims in
   `pg_version_compat.h`; distributed/columnar header adaptations for
   renamed/retyped PG19 APIs; 128 KB shmem slack on PG19 for the new
   `ShmemInitHash` up-front allocation, plus the `maintenanced.c`
   double-count fix that change exposes.
3. **-Werror source fixes** — warning fixes across 56 files (old-style
   prototypes, strict forward declarations, implicit-fallthrough, new
   switch arms, API renames). **No compiler-flag demotion** — the build
   keeps the same warning/error flags as PG17/PG18.
4. **ruleutils_19.c scaffold** — clone of `ruleutils_18.c` retargeted at
   the PG19 version guard so the binary links (per the #7725 / #8010
   precedent; the real upstream-derived port lands in a follow-up PR).
   Excluded from citus-style via `.gitattributes` — it is an
   upstream-derived clone exceeding the CI uncrustify 10,000-line limit.
5. **test** — add PG19 to the oldest-supported Citus version matrix.

## Scope / non-goals

- **Build path only.** PG19 is intentionally NOT added to `test-citus`,
  `test-citus-failure`, `test-citus-cdc`, or the upgrade jobs: the
  regression suite needs the upstream-derived `ruleutils_19.c` from the
  follow-up PR, and no released Citus supports PG19 yet. Running the
  suite on this PR alone produces silent PG18-on-PG19 deparse
  corruption.
- `multi_explain.c` is touched only for the `Instrumentation` /
  `NodeInstrumentation` rename; a separate `TupleDescFinalize` runtime
  fix lands in a later PR.

## Validation

- PG19devel / PG17.10 / PG18.4: build exits 0 (validated in CI images).
- `CREATE EXTENSION citus` succeeds on PG19.
- No regression-suite changes here.

Refs: #8597
DESCRIPTION: Port upstream PG19 ruleutils.c into Citus deparser tree.

Mirrors #8010 (PG18) and #7725 (PG17). Replaces the ruleutils_19.c
placeholder from the build-foundation PR with a proper port of upstream
PG19's src/backend/utils/adt/ruleutils.c, produced by a 3-way merge
(git merge-file: ruleutils_18.c as base, upstream PG18 and upstream
PG19 as the two sides). All merge conflicts fell in blocks Citus had
already stripped from _18.c or in shard-aware deparse variants, and were
resolved by keeping the Citus side. The file keeps Citus' existing
curation (deparse hooks, shard-aware UPDATE/DELETE branches) while
picking up real upstream PG18->PG19 deparse changes.

Without this, Citus on a PG19 backend would deparse worker-bound SQL
using PG18 semantics -- silent corruption the moment a PG19-only node,
clause, or formatting decision appears in the tree.

The trailing #endif guard text is corrected to
"(PG_VERSION_NUM >= PG_VERSION_19) && (PG_VERSION_NUM < PG_VERSION_20)"
(the _17.c/_18.c snapshots carry stale guard comments; not propagated).
The generate_function_name fgc_flags fix is absorbed naturally by the
merge -- upstream PG19 already passes &fgc_flags at that call site.

Excluded from citus-style via .gitattributes (added in the
build-foundation PR): the file exceeds the CI uncrustify 10,000-line
limit and is upstream-derived.

Refs: #8597


---
Stacked on #8601 (`pg19-build-foundation`).
Turns on the PG19beta1 columns across the downstream Build & Test
matrices now that the `build` job already proves PG19 compiles under
`-Werror` and the ruleutils port (#8602) lands the runtime/regress
fixes those suites depend on.

### Changes (`.github/workflows/build_and_test.yml`)

- `test-citus`, `test-citus-failure`, `test-citus-cdc`: add
  `pg19_version` to the `pg_versions` array.
- `test-arbitrary-configs`: add `pg19_version` to `matrix.pg_version`.
- `test-pg-upgrade`: add the `18 -> 19` and `16 -> 19` upgrade pairs
  (adjacent + oldest -> newest, consistent with the existing
  16->17 / 17->18 / 16->18 set).
- `params`: bump `pg19_version` / `upgrade_pg_versions` from `19devel`
  to `19beta1`, and point `image_suffix` at the PG19beta1-enabled
  the-process images (`-dev-33b3cf5`, built from
  citusdata/the-process#222).

### Deliberately not changed

- `test-citus-upgrade` stays at PG16/PG17 — no released Citus binary
  supports PG19, so there is nothing to upgrade *from*.

### Dependencies

- Image set: citusdata/the-process#222 (PG19beta1 via apt) — **green**,
  images published under `-dev-33b3cf5`.
- Runtime/regress fixes: ruleutils port #8602 plus the
  runtime/regress sub-issues must be on `pg19-support` for the new
  cells to pass.

Closes #8615
Part of #8597
Stacked PR (PR2 of the PG19 stack). **Base = `pg19-ci-test-matrices`
(#8616)**, which is itself stacked on `pg19-ruleutils-port` (#8602) →
`pg19-support`. Review/merge in stack order.

Fixes two PG19 runtime crashes that surface in normal Citus operation
(outside the regression suite):

1. **`FuncnameGetCandidates()`** gained an `int *fgc_flags`
out-parameter that the callee writes to unconditionally. The compat shim
passed `NULL` and crashed on every by-name function lookup (e.g. the
maintenance daemon). Now passes the address of an int compound literal.
2. **`TupleDesc->firstNonCachedOffsetAttr`**: PG19 added an offset cache
that `BlessTupleDesc()` / `slot_deform_heap_tuple()` now assert is
populated but no longer populate themselves. `BlessTupleDesc` is wrapped
to call `TupleDescFinalize()` first (no-op shim on older majors), with
explicit calls added at the four sites that deform hand-built
TupleDescs.

Also installs `ApplicationNameAssignHook` via union access on PG19.

Closes #8610
Part of #8597
Stacked PR (PR3 of the PG19 stack). **Base = `pg19-runtime-fixes`
(#8617)** → `pg19-ci-test-matrices` (#8616) → `pg19-ruleutils-port`
(#8602) → `pg19-support`. Review/merge in stack order.

PG19 enables parallel `CREATE INDEX` by default
(`max_parallel_maintenance_workers` defaults to 2). Columnar's TableAM
is always serial — `rs_parallel` is stored but never read, and the build
path flushes pending writes, which is disallowed inside a parallel
operation. This caused `CREATE INDEX` on columnar tables to fail on
PG19.

Three changes (all in `columnar_tableam.c`):

1. **`parallelscan_estimate`/`initialize`/`reinitialize`** now delegate
to the `table_block_*` helpers, so callers that unconditionally size and
initialize a `ParallelTableScanDesc` (e.g. PG19's parallel btree build)
no longer abort. Columnar ignores the descriptor, so the written state
is simply unused.
2. **`columnar_index_build_range_scan`** accepts a parallel scan
descriptor by discarding it (`scan = NULL`) and running the serial path.
3. **`ColumnarProcessUtility`** forces
`max_parallel_maintenance_workers=0` (via
`NewGUCNestLevel`/`AtEOXact_GUC` around `PrevProcessUtilityHook`) for
`CREATE INDEX` on a columnar AM relation, so the build runs serially.

Closes #8611
Part of #8597

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…8619)

Stacked PR (PR3b of the PG19 stack). **Base = `pg19-columnar-index`
(#8618)** → `pg19-runtime-fixes` (#8617) → `pg19-ci-test-matrices`
(#8616) → `pg19-ruleutils-port` (#8602) → `pg19-support`. Review/merge
in stack order.

PG19 removed `get_relation_info_hook`, which is where pre-PG19 builds
disable parallel query and index-only scans for columnar relations
(columnar scans are always serial and cannot return tuples from an
index). The Phase-1 build left a `TODO(PG19, #8614)` placeholder; this
PR re-implements that suppression inside `ColumnarSetRelPathlistHook`:

- forbid future parallel workers (`rel_parallel_workers = 0`,
`consider_parallel = false`) and drop any already-generated partial
paths;
- clear each index's `canreturn` flags and strip any `IndexOnlyScan`
paths that were already created (`set_rel_pathlist_hook` runs after path
generation).

The pre-PG19 `ColumnarGetRelationInfoHook` path is unchanged.

Closes #8614
Part of #8597

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
## What

Restores partitioned-index stripping on PostgreSQL 19, which regressed
when PG19 removed the `get_relation_info_hook` that Citus relied on.
This is a PG19-only planner-hook migration and a no-op on PG ≤ 18.

The columnar `scan_analyze_next_tuple` signature item from the original
scope was already fixed in the build-foundation PR #8601 and is
intentionally **not** touched here.

## Why

PG19 dropped `get_relation_info_hook` in favour of
`build_simple_rel_hook`. Citus used that hook
(`multi_get_relation_info_hook`) to strip partitioned indexes from
partitioned-table plans. PR #8601 disabled it on PG19 with a `TODO(PG19
Phase 2)` marker, so index-stripping was temporarily absent on PG19,
causing a client-backend crash on partitioned-table planning paths
inside the regression suite (`make check`).

## How

- Add a PG19-only `multi_build_simple_rel_hook` in
`distributed_planner.c` (prototype in `distributed_planner.h`) matching
the `build_simple_rel_hook` signature `(PlannerInfo *root, RelOptInfo
*rel, RangeTblEntry *rte)`. It delegates straight to the existing
`multi_get_relation_info_hook` body, which re-derives the
`RangeTblEntry` from `rel->relid` — so the removed `relationObjectId` /
`inhparent` arguments are passed as `InvalidOid` / `false` and
intentionally ignored, keeping behaviour identical across versions.
- Register it via `build_simple_rel_hook` under `#if PG_VERSION_NUM >=
PG_VERSION_19` in `shared_library_init.c`; the old
`get_relation_info_hook` assignment is kept under the `#else` arm for PG
≤ 18.

## Files

- `src/backend/distributed/planner/distributed_planner.c`
- `src/include/distributed/distributed_planner.h`
- `src/backend/distributed/shared_library_init.c`

Item 1 (`src/backend/columnar/columnar_tableam.c`) is untouched —
already fixed in #8601.

## Scope note

An earlier revision of this PR also carried a `distribution_column.c`
`AccessShareLock` change ("item 3"). That fix addresses a **PG16+**
`relation_open()` assertion / relcache-lifetime issue that is not
PG19-specific, so it has been **split out** into a separate PR targeting
`main`. This PR is now scoped solely to the PG19 `build_simple_rel_hook`
migration.

## Stacking

Stacked on #8619 — base branch is `pg19-columnar-relinfo`. GitHub will
auto-retarget this PR to `pg19-support` once #8619 merges.

## Cross-version gate

- [x] **PG19 (19beta1):** full `-Werror` build green;
`partitioned_indexes_create` (+ setup) regress passed, **zero diff** —
exercises the partitioned-index planning path and proves the hook is
active.
- [x] **PG18 (18.4):** full `-Werror` build green; same focused regress
passed, **zero diff** — the pre-PG19 `#else` arm is unchanged and
neutral.
- [x] **PG17 (17.10):** the migration is preprocessor-excluded (`#if
PG_VERSION_NUM >= PG_VERSION_19`), so PG17 is structurally unaffected;
the earlier full-scope gate confirmed a green PG17 `-Werror` build +
regress.
- [x] **check-style:** `citus_indent --check` clean on all touched
files.

All arms build clean under full `-Werror` (no flag demotion). The change
is behaviourally confined to PG19; PG ≤ 18 is preprocessor-excluded and
proven regression-neutral.

Part of #8597. Relates to #8608.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Part of #8597. Closes #8609 _(note: GitHub auto-close won't fire on a
non-`main` base)._

## Problem

PG19 internalised LWLock tranche registration: `LWLockNewTrancheId()`
now takes the tranche name and returns the id in one step, and the
public two-step `LWLockRegisterTranche()` was removed (names now live in
shared memory, visible to every backend). The PG19 foundation work
(#8601) added a temporary shim that registered **every** Citus tranche
under the placeholder name `"citus-deferred"`, so on PG19 every Citus
lightweight-lock wait surfaced as `citus-deferred` in
`pg_stat_activity.wait_event` — a diagnostics regression vs PG&le;18
(ids stayed unique, so not a correctness bug, hence deferred to this
PR).

## Change

- Replace the `citus-deferred` placeholder shim in `pg_version_compat.h`
with a single `LWLockNewTrancheIdCompat(name)` helper:
- **PG19**: macro forwarding the name to one-step
`LWLockNewTrancheId(name)`.
- **PG&le;18**: a `static inline` keeping the historical
allocate-then-register two-step behind the same interface.
- Thread the real per-subsystem name through at all 7 call sites and
drop the standalone `LWLockRegisterTranche()` calls.
- Remove the `pg_attribute_unused()` markers the foundation work added
on the now-used `trancheName` locals (`shardsplit_shared_memory.c`,
`backend_data.c`).
- Keep the `NamedLWLockTranche` compat typedef (still required by Citus
shmem structs on PG19).

Call sites: `causal_clock.c`, `shared_connection_stats.c`,
`shardsplit_shared_memory.c`, `cluster_changes_block.c`,
`stat_tenants.c`, `maintenanced.c`, `backend_data.c`.

PG&le;18 behaviour is unchanged (identical allocate-then-register
sequence); PG19 now reports the correct per-subsystem tranche name.

## Validation (WSL, pgenv)

Each version built under full `-Werror` (no flag demotion), then a
single-node functional check: `CREATE EXTENSION citus`, followed by
16-session contention on the cluster-clock LWLock
(`citus_get_node_clock()`) while sampling `pg_stat_activity.wait_event`.

| Version | `-Werror` build | warnings in touched files | `CREATE
EXTENSION` | `wait_event` observed | `citus-deferred` |
|---|---|---|---|---|---|
| 17.10 | clean | none | OK | `Cluster Clock Setup Tranche` | 0 |
| 18.4 | clean | none | OK | `Cluster Clock Setup Tranche` | 0 |
| 19beta1 | clean | none | OK | `Cluster Clock Setup Tranche` | 0 |

PG19 confirms the real per-subsystem name now surfaces (no longer
`citus-deferred`); PG17/18 confirm the two-step path is
regression-neutral. The one runtime-observable tranche (cluster clock)
is verified directly; the other six use the identical helper and are
covered by the `-Werror` build. Full multi-node regression runs in CI.

---
**Draft** — held for review per the PG19 support effort coordination.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DESCRIPTION: Normalize PostgreSQL 19 cosmetic output differences without
weakening existing regression or expected-output oracles.

Addresses #8659.

## Stack

This PR is stacked on #8622 and targets `ihalatci-pg19-regress-crashes`.
The planner crash fixes must land before these output normalizations
because they expose several normalized regression paths.

## Changes

Add narrowly scoped PG19 compatibility rules in
`src/test/regress/bin/normalize.sed`. PG19 output is mapped back to
canonical PG17/PG18 output; canonical expected files are left unchanged.

The final rules preserve plan numbers, deparsed worker queries, CTE
identity outside the affected line, tuple byte counts outside the two
observed deltas, and generic function-resolution diagnostics outside the
exact PG19 forms.

## Rule provenance

| Added normalization rule | PG19 source commit | Attribution |
|---|---|---|
| Named `InitPlan`/`SubPlan` references and nested EXISTS numbering |
`d3790f2af` | Inferred from "PG19 EXPLAIN cosmetic differences" |
| Implicit `_ANY_subquery` naming | `64c9585de` | Inferred from
"EXPLAIN/error output differences" |
| `VACUUM ... PARALLEL` error wording | `64c9585de` | Inferred |
| Partitioned-table primary-key error wording | `64c9585de` | Inferred |
| Multiline `\d+` `Partitions:` lists | `0247b7ef1` | Directly
documented |
| `count(non-null-column)` / `count(*)` plan output | `d3790f2af` |
Inferred from EXPLAIN changes |
| Inlined CTE name suffix | `d3790f2af` | Inferred from EXPLAIN changes
|
| Exact tuple-data byte deltas | `2119b6796` | Directly documented |
| Function-resolution `HINT`/`DETAIL` differences | `2119b6796` |
Directly documented |
| Extra PL/pgSQL function context | `11b27a791` | Directly documented |
| Temporary-sequence dependency `DETAIL` | `11b27a791` | Directly
documented |
| Implicit foreign-key name suffix | `11b27a791` | Directly documented |
| Append-partitioned shard length `139264` -> `131072` | `11b27a791` |
Directly documented |

The original Git objects are unavailable on the current remotes and
searched local/WSL repositories. The split between `64c9585de` and
`d3790f2af` is therefore inferred from their documented subjects; the
other mappings are explicitly supported by the local PG19 plan.

## Validation

- PostgreSQL 17.10, 18.4, and 19beta1 build cleanly with
`CFLAGS=-Werror`.
- PG17/PG18 `check-multi`, `check-multi-1`, and `check-multi-mx` pass
apart from the existing `multi_extension.out` MX baseline.
- PG19 `check-multi-1` passes. `check-multi` leaves only the four
planned downstream files (`clock.out`, the two order-by files, and
`multi_subquery_in_where_reference_clause.out`); `check-multi-mx` leaves
only `multi_extension.out` and planned `multi_mx_explain.out`.
- Applying the final normalizer to all 835 canonical expected files
changes zero files.
- Replaying PG19 CI artifacts resolves 21 intended result diffs: 12 in
`check-multi`, 7 in `check-multi-1`, 1 in `check-multi-mx`, and 1 in
`check-split`.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… (#8624)

Part of #8597 (PostgreSQL 19 support).

Addresses #8613 — dispatch and discriminate the PG19 unified
REPACK/CLUSTER command (`T_RepackStmt`).

> Note: the base branch of this PR is `pg19-support` (the PG19
integration branch), not `main`, so GitHub will **not** auto-close #8613
on merge — it is tracked manually.

## What & why

PG19 removed `ClusterStmt`/`T_ClusterStmt` and replaced them with the
unified `RepackStmt`, which backs three commands distinguished by
`RepackStmt.command`: `CLUSTER`, the new `REPACK`, and `VACUUM FULL`.
(VACUUM FULL still dispatches through `T_VacuumStmt`, so it never
reaches this path.) The foundation PR (#8601) added a compile-only shim
aliasing `T_ClusterStmt` → `T_RepackStmt`; that makes the names compile
but is not correct behaviour, because CLUSTER and REPACK now collide on
a single node tag and must be told apart.

This PR makes Citus dispatch and propagate the command correctly on
PG19, while staying byte-for-byte regression-neutral on PG17/PG18.

## Approach (Option A)

Citus propagates **REPACK exactly like CLUSTER**: the command is shipped
to every shard placement through the existing CLUSTER code path. The
worker name-relay (`RelayEventExtendNames` → `AppendShardIdToName`)
mutates the parse tree in place, appending the shardId to **both** the
target relation name **and** the index name (when `USING INDEX` is
given), then `ProcessUtilityParseTree` executes the mutated node — there
is no deparse-to-string step, so the existing relabel localizes REPACK's
names with no new deparser work. CLUSTER vs REPACK are discriminated by
`RepackStmt.command`, and the user-facing WARNING/ERROR wording is
command-aware.

## Files

- `src/include/pg_version_compat.h` — RepackStmt shim + version-portable
helpers `ClusterStmtIsRepack()` / `ClusterStmtCommandName()` (compiled
out on `< PG19`).
- `src/backend/distributed/commands/cluster.c` — `PreprocessClusterStmt`
is command-aware (correct REPACK vs CLUSTER wording in the no-relation
WARNING, the partitioned WARNING, and the VERBOSE ERROR).
- `src/backend/distributed/commands/distribute_object_ops.c` —
`GetDistributeObjectOps` routes both CLUSTER and REPACK (shared
`T_ClusterStmt`/`T_RepackStmt` tag).
- `src/backend/distributed/relay/relay_event_utility.c` — name-relay
reads the RepackStmt layout (relation + index name).
- `src/test/regress/sql/pg19.sql`, `expected/pg19.out`,
`expected/pg19_0.out` — new PG19-only acceptance test (+ the `< PG19`
early-quit variant).
- `src/test/regress/multi_1_create_citus_schedule` — registers the
`pg19` test.

## Acceptance test (PG19-only, `pg19.sql`)

The test distributes `repack_test` (4 shards, replication factor 1) and
**demonstrates** (not merely asserts) that distributed REPACK works:

- `REPACK <t> USING INDEX <idx>`, bare `REPACK <t>`, and `CLUSTER <t>
USING <idx>` each **rewrite every shard placement** — the relfilenode
changes on all shards (verified via `run_command_on_shards`).
- **Distribution-invariance** after the rewrites — proves REPACK changes
*storage*, not *distribution semantics*:
- `placements_unchanged` — shard set + placement nodes identical
before/after (symmetric `EXCEPT`).
  - `shard_count_unchanged` — shard count identical before/after.
- `shard_row_mapping_unchanged` — per-shard `count(*)` identical
before/after (no row crossed a shard boundary).
- `distribution_unchanged` — `(partmethod, partkey, colocationid)`
intact.
- routing still works — router queries `a = 42 → (42, 2)` and `a = 100 →
(100, 0)`; cross-shard aggregate `count = 100, sum = 5050`.
- Command-aware messaging — `VERBOSE` → REPACK/CLUSTER-worded ERROR;
partitioned distributed table → `not propagating REPACK command for
partitioned table to worker nodes` WARNING.

The `< PG19` early-quit path keeps PG17/PG18 on the unchanged
`pg19_0.out`, so this test is invisible to older majors.

## Validation gate (WSL, pgenv, full `-Werror`, no demotion)

| Version | Build (`-Werror`) | Regress |
|---|---|---|
| PG17.10 | clean | 4/4, no diffs (pg19 early-`\q` → unchanged
`pg19_0.out`) |
| PG18.4 | clean | 4/4, no diffs (pg19 early-`\q` → unchanged
`pg19_0.out`) |
| PG19beta1 | clean | 4/4 — distributed-REPACK acceptance test green
(all invariance assertions `t`) |

CLUSTER's existing tests/expected (`multi_index_statements`, `pg15`) are
unchanged on all versions.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8fc5a222-5fb6-48fd-9b27-cd0a418201d2
## Summary

- make the partition-to-partition `fkey` and
partition-to-reference-table `fkey_to_ref` checks mutually exclusive
- use one cross-version expected output for PostgreSQL 17, 18, and 19
- remove the now-redundant numbered alternative expected output
- leave `normalize.sed` and production code unchanged

## Coverage fix

The test previously used `(id, value_1) = (0, 5)` while both foreign
keys were unsatisfied. PostgreSQL 17/18 reported `fkey` first, while
PostgreSQL 19beta2 reported `fkey_to_ref` first, so the row did not
independently prove either check.

The revised sequence makes each failure unambiguous:

1. Seed reference key `5`, then insert `(0, 5)` before partition
`referencing_table_0` contains `id = 0`. Only `fkey` is unsatisfied:

   ```text
   foreign key constraint "fkey_xxxxxxx"
   Key (id)=(X) is not present in table "referencing_table_0_xxxxxxx".
   ```

2. Insert `(0, 1)` into partition `referencing_table_0`, satisfying
`fkey` for `id = 0`. Then insert `(0, 4)`, where reference key `4` is
absent. Only `fkey_to_ref` is unsatisfied:

   ```text
   foreign key constraint "fkey_to_ref_xxxxxxx"
   Key (value_1)=(4) is not present in table "referenced_table_xxxxxxx".
   ```

3. Insert `(0, 5)` successfully once both constraints are satisfied.

## Validation

| PostgreSQL | Focused test | Expected output |
|---|---|---|
| 17.10 | Passed | Primary, byte-for-byte |
| 18.4 | Passed | Primary, byte-for-byte |
| 19beta2 | Passed | Primary, byte-for-byte |

Each leg used a fresh Citus build with absolute PostgreSQL installation
paths. The focused schedule included its required helper tests and all 5
tests passed. All three normalized results were identical (`sha256
8b6ab879b0581fed4fd1efbe023d896c00513c42f1d0b95ad8edddf523ae3527`). The
primary output is normalization-idempotent, and `git diff --check`
passes.

Closes #8669

> [!NOTE]
> This draft targets the non-default `pg19-support` branch. GitHub's
closing keyword takes effect only when the change reaches the
repository's default branch.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Copilot-Session: 2a53d4d7-c12a-41cc-96ea-46be67eff49d
## Summary - suppress planner cost estimates in the two `clock` EXPLAIN
checks that assert `cc_idx` usage - order all nine previously unordered
PG17 partition-listing queries by `partition_name` - update only the
corresponding regression expected output ## Validation All validation
used WSL and absolute PostgreSQL installation paths. - PostgreSQL 17.10:
`CFLAGS=-Werror` build/install; focused `clock` (7/7) and `pg17` (9/9)
passed - PostgreSQL 18.4: `CFLAGS=-Werror` build/install; focused
`clock` (7/7) and `pg17` (9/9) passed - PostgreSQL 19beta2:
`CFLAGS=-Werror` build/install; focused `clock` (7/7) and `pg17` (9/9)
passed - `citus_indent --quiet --check` - `git show --check` Closes
#8668. GitHub will only auto-close the issue once this change becomes
reachable from the default branch.

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Copilot-Session: 17c170a6-c9fa-40c1-b7af-b060988224fc
Stabilize PostgreSQL 19 ORDER BY pushdown plan assertions ## Context
PostgreSQL BUG #19517 caused eager aggregation on semi/antijoin inner
relations to return incorrect results on 19beta1 (`5|130`). Upstream
commit `ffeda04259bb` fixes that bug, and the same reproducer returns
the correct `5|26` on 19beta2. This PR therefore does not disable
`enable_eager_aggregate` or `enable_hashagg` globally or in the
regression harness. The H4 case in `multi_orderby_pushdown` is
independent of that correctness fix. On 19beta2, with eager aggregation
at its default, it still chooses a worker `Nested Loop` plan instead of
the asserted `Sort -> Hash Semi Join` shape. Both `Citus Sorted Merge
Adaptive` nodes remain, so pushdown itself is intact. This change
stabilizes only the H4 EXPLAIN by wrapping it in a transaction and
propagating transaction-local planner settings: ```sql BEGIN; SET LOCAL
citus.propagate_set_commands TO 'local'; SET LOCAL enable_nestloop TO
off; -- existing H4 EXPLAIN COMMIT; ``` The expected plan is otherwise
unchanged and continues to assert sorted-merge pushdown and the worker
hash semi-join shape. There are no production-code or regression-harness
changes. ## Validation - PostgreSQL 17.10: `-Werror` build and focused
`multi_orderby_pushdown` path, 15/15 passed. - PostgreSQL 18.4:
`-Werror` build and focused path, 15/15 passed. - PostgreSQL 19beta2:
`-Werror` build and focused path, 15/15 passed with default
`enable_eager_aggregate=on`. - PostgreSQL 19beta2 upstream regression
suite: 245/245 passed, including `eager_aggregate`. - PostgreSQL 19beta2
Citus semi/antijoin path: `multi_subquery_in_where_reference_clause`,
15/15 passed with eager aggregation enabled. - Perl syntax, style, and
diff whitespace checks passed. Closes #8666 > This draft targets the
non-default `pg19-support` branch. GitHub may defer automatic issue
closure until the change reaches the default branch.

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Copilot-Session: bd1ff9e9-2f27-43d3-b9d7-f7d167e7e3c4
## Summary

- disable `citus.allow_aggregate_worker_combine_on_internal_types` in
`tdigest_aggregate_support` so the regression test continues to assert
tdigest-specific distributed plan shapes instead of the generic
internal-state worker-combine path
- update the primary expected output and the existing `_0`/`_1`
alternatives with the new `SET` echo
- keep the existing alternative semantics: `_0` covers extension-absent
output and `_1` covers accepted tdigest result-value variation
- account for PostgreSQL 19 Beta 2's undefined-function `DETAIL` through
the normalization already present on the refreshed `pg19-support` base
(`ca86a5d95`); a separate `_2` alternative was validated during
development but removed because that base normalization makes it
redundant and unreachable

## Validation

- `-Werror` builds: PostgreSQL 17.10, 18.4, and 19 Beta 2
- focused `tdigest_aggregate_support` schedule with tdigest installed:
passed on PostgreSQL 17.10, 18.4, and 19 Beta 2
- focused schedule without tdigest: PostgreSQL 17.10 and 18.4 matched
`_0`; PostgreSQL 19 Beta 2's raw `DETAIL` normalized and matched `_0`
- `make check-style`
- `git diff --check`

Closes #8667

> This PR intentionally targets the non-default `pg19-support` branch.
GitHub may not automatically close #8667 when this merges there; closure
should be confirmed when the support branch is subsequently integrated
into the default branch.

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 242038e7-c2b1-4341-b25d-a9f69a4e9981
## Summary
- exclude PostgreSQL 19 packaging tests for Oracle Linux 8 and AlmaLinux
8, where PGDG packages are unavailable
- exclude PostgreSQL 19 packaging tests for Ubuntu focal, while
preserving the existing PostgreSQL 18 exclusion
- keep PostgreSQL 19 coverage for AlmaLinux 9, Debian bullseye/bookworm,
and Ubuntu jammy

## Validation
- parsed the workflow YAML
- verified the expanded PostgreSQL 19 matrix retains only supported
packaging images
- verified the branch is exactly one commit ahead of `pg19-support` with
a one-file diff

Tracks #8731.

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	.github/workflows/build_and_test.yml
Recognize PostgreSQL 19 property-graph dependency object classes in Citus compatibility code.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Stabilize PostgreSQL 19 planner-sensitive output and make changed-test schedule dependencies repeat-safe.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Resolve keyword RoleSpec targets safely in REASSIGN OWNED and cover multi-node propagation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Normalize PostgreSQL 19 unnamed subquery labels and update the canonical expected output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Fix PG19 publication exclusion deparsing, metadata activation, late distribution, and OID-rewrite membership restoration, with reviewed reference-table coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Mark the required Citus metadata catalogs accessible to PG19 logical decoding and intentionally preserve that runtime invariant across extension downgrade.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
Closes #8732.

## Summary

- pin `pglz` only around the heap TOAST-size assertion in
`columnar_insert`
- extend the existing EXPLAIN normalizer for PG19 `SubPlan any_N` names
- add the PG19 alternate `VACUUM FULL` status output for
`columnar_vacuum_vs_insert`

All three failures are PostgreSQL output/configuration drift that
predates `REL_19_BETA1`, not Beta2 regressions or columnar compatibility
defects:

- PostgreSQL `34dfca293432` made LZ4 the default TOAST compression when
available
- PostgreSQL `8c49a484e8eb` introduced textual subplan names such as
`any_1`
- PostgreSQL `ac58465e0618` introduced REPACK and the `repacking ... in
physical order` wording

Direct Beta1 and Beta2 runs reproduce the same behavior for all three
cases.

## Tests

- PG17.10: `check-columnar-minimal EXTRA_TESTS="columnar_insert
columnar_chunk_filtering"`; `check-columnar-isolation`
- PG18.4: `check-columnar-minimal EXTRA_TESTS="columnar_insert
columnar_chunk_filtering"`; `check-columnar-isolation`
- PG19beta2: `check-columnar-minimal EXTRA_TESTS="columnar_insert
columnar_chunk_filtering"`; `check-columnar-isolation`

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
## Summary

- send WAL receiver apply-status replies once per second in the Perl
follower-cluster test harness
- match the existing Python test harness behavior
- preserve PostgreSQL and Citus production defaults

## Provenance

- published from the explicitly approved commit
`5e4ddc836a95df6cea9cceef33cb44d48559b736`
- based directly on `pg19-support` at
`ff8f1f8c46de4c3ed094c99796c9805529d1a328`
- contains only the one-line `wal_receiver_status_interval=0` to `1`
test-harness change

## Testing

- PG19beta2 before fix: `make -C src/test/regress
check-follower-cluster` timed out after 180 seconds while creating the
follower topology
- PG17.10 after fix: 8/8 follower-cluster tests passed
- PG18.4 after fix: 8/8 follower-cluster tests passed
- PG19beta2 after fix: 8/8 follower-cluster tests passed

Closes #8736

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reconstruct partitioned-root publications from explicit catalog identities while preserving runtime leaf semantics, with expanded reviewed coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1782b0d-8a8c-4d1a-9d71-9c58afe11a7d
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.65432% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.74%. Comparing base (3164f62) to head (0eb449c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8753      +/-   ##
==========================================
+ Coverage   88.69%   88.74%   +0.05%     
==========================================
  Files         289      289              
  Lines       64889    65070     +181     
  Branches     8180     8201      +21     
==========================================
+ Hits        57553    57747     +194     
+ Misses       4973     4959      -14     
- Partials     2363     2364       +1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ihalatci-msft and others added 4 commits August 15, 2026 17:17
Allow only the output plugins exercised by PG19 test clusters and remove the reverted GROUP BY ALL deparser access.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pin the PG19 build and upgrade path to beta3 and use the matching development image.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Several CDC cases exercise nonblocking shard operations that create logical slots using the citus output plugin. Trust it only in PG19 CDC test clusters alongside pgoutput and wal2json.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Quote EXTRACT field identifiers in the PG19 ruleutils copy so crafted field names cannot escape into a second statement in distributed task SQL. Add a PG19 regression that exercises a malicious field name and verifies no worker-side object is created.

Ported from PostgreSQL security commit a3832a7571013469dc81e35d0fe1c0066e86d893 (CVE-2026-15741).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@ihalatci ibrahim halatci (ihalatci) changed the title Pg19 support (do not merge yet) Pg19 support Aug 19, 2026
Fix PG19 relation JSON COPY TO routing.

## Problem

Relation `COPY ... TO STDOUT (FORMAT json)` used Citus's direct shard
COPY
fast path. Concatenating shard-formatted JSON produced multiple arrays
instead
of one valid JSON document for `FORCE_ARRAY true`. The fast path also
reported
incorrect COPY protocol metadata and command tuple counts, especially
for empty
results.

## Fix

- exclude `FORMAT json` from the direct `CitusCopyTo()` path so
PostgreSQL
  formats one coordinator-owned JSON stream through `CitusCopySelect()`
- construct the coordinator SELECT from `CopyGetAttnums()` so explicit
normal
  columns preserve order while generated, system, missing, and duplicate
  columns retain native relation COPY validation
- cover multi-shard, empty, single-shard, reference, local, Citus-local,
  streaming, explicit-column, escaping/null, encoding, option-error, and
  low-level libpq protocol behavior

This intentionally trades direct shard streaming for coordinator query
execution. Large JSON COPY operations may therefore use coordinator
batching
or spill, but PostgreSQL must own the whole JSON document framing and
protocol
state for correctness.

## Validation

- PostgreSQL 16.14, 17.10, 18.4, and 19beta2 builds with
`CFLAGS=-Werror`
- `multi_copy` regression: 15/15 passed on PostgreSQL 16, 17, and 18
- combined `multi_copy` and `pg19` regression: 16/16 passed on
PostgreSQL 19
- focused low-level libpq protocol/equivalence test: passed
- targeted Black, isort, flake8, `citus_indent`, and diff checks: passed

Closes #8760

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1aa55534-88b0-4ef8-91b5-adba3eec71b1
## Summary

Preserve a table `GRANT`/`REVOKE` statement's explicit `GRANTED BY` role
when Citus deparses and propagates the command to physical shards.

This PR is intentionally limited to table-grantor serialization in
`commands/grant.c`. It is separate from #8766, which fixes shared-object
`REVOKE` clause ordering in the deparser. The two are complementary
halves of the same report and must not be conflated.

Tracks #8759
Umbrella tracking: #8597

## Root cause

`PreprocessGrantStmt()` hand-builds the worker DDL string rather than
routing through the deparser, and it never emitted `GrantStmt.grantor`.
The explicit grantor was therefore silently dropped on the way to every
worker.

PostgreSQL 19 (commit `dd1398f1`) widened `GRANTED BY` to accept **any
role the acting role inherits**, not just `current_user`. Once the
grantor is dropped from the worker command, each worker independently
runs `select_best_grantor()` and can pick a different role than the
coordinator did. The result is a coordinator/shard ACL divergence.

On PG16-18 the same statement requires `grantor = current_user`, so the
omission is unobservable there. This is why the defect is only
reachable, and only testable, on PG19.

## Impact

Once the ACLs diverge, a later `REVOKE` that names the coordinator's
grantor matches nothing on the shards. Concretely, the coordinator
reports the privilege as revoked while the physical shards still grant
it — a silent privilege leak that `has_table_privilege()` on the
coordinator will not reveal.

## Fix

`commands/grant.c` only, +16/-4. Builds a `grantedBy` fragment when
`grantStmt->grantor` is set and appends it to both format strings. In
`REVOKE` it lands after the grantee list and before
`CASCADE`/`RESTRICT`, which is the same ordering #8766 establishes for
shared objects, so the two scopes agree by construction.

`GrantStmt.grantor` has existed since PG14, so no version gate is
needed; the code is inert on PG16-18 where the field cannot be set to
anything but the current role.

## Test design

The scenario is deliberately discriminating. A third-party role owns the
table and is **never named as grantor**, so the non-discriminating
`owner == grantor` case cannot mask a failure. Two eligible grantors are
inherited by the acting role, and the statement explicitly names the one
the workers would *not* choose on their own.

Coverage:
- coordinator vs physical-shard `aclexplode(relacl).grantor` parity,
read through `run_command_on_shards()`
- selective-revoke parity via `has_table_privilege()` on coordinator and
shards
- a quoted grantor role name (`"Grant Owner"`), exercising
`RoleSpecString(..., true)`
- `WITH GRANT OPTION` and the matching `REVOKE GRANT OPTION FOR`

Tests are gated on `server_version_ge_19` and are pure additions: zero
removed lines in either `pg19.sql` or `pg19.out`.

## Negative control

Reverting `grant.c` to stock, rebuilding, reinstalling and re-running
turns `pg19` red. Verified twice in independent cycles, the second time
with a binary-level gate confirming the built `citus.so` actually
matched the intended source in each phase (`strings citus.so | grep -c
'GRANT %s ON %s TO %s%s%s'` -> stock 0, fixed 1).

Four assertions flip, and **every one of them is shard-side**. All
coordinator rows are byte-identical between the two runs and appear only
as unchanged context:

| assertion | stock (broken) | with fix |
|---|---|---|
| shard grantor | `pg19_grantor_a` | `pg19_grantor_b` |
| shard privilege after selective revoke | `f` (coordinator reads `t`) |
`t` |
| shard grant option after `GRANT ... WITH GRANT OPTION` |
`pg19_grantor_a, grantable=true` | `"Grant Owner", grantable=true` |
| shard grant option after `REVOKE GRANT OPTION FOR` | `pg19_grantor_a,
grantable=false` | `"Grant Owner", grantable=false` |

The sharpest single line is row 2: `coordinator_privilege = t` sits two
lines above `shard_privilege = f` as unchanged context in the same diff
hunk.

**Disclosed limitation, stated plainly:** rows 3 and 4 discriminate on
grantor *identity* only, not on the `grantable` flag. Unfixed, the shard
ACL entry and the un-attributed revoke both resolve to `pg19_grantor_a`
and therefore coincide, so the grant option is still correctly dropped.
Rows 1 and 2 carry the proof; rows 3 and 4 are corroborating.

## Validation

- base is exactly `origin/pg19-support` @ `db67cd7e2` with zero
intervening delta, so the validated tree is the pushed tree
- 3 files, +305/-4; the two test files are append-only
- `citus_indent --check` clean (exit 0, no `FAIL` lines)
- full-cluster `pg19` regression on a 3-node PostgreSQL 19beta2 harness:
`ok` with the fix, zero diff
- `expected/pg19.out` is harness-generated, never hand-written, and
re-verified byte-identical to a freshly generated `results/pg19.out`

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
## Summary

PostgreSQL psql change `d6ab88d`, first present in PG19 Beta3, changed
failed COPY input draining. These four failure injections abort before
COPY_IN, so inline `\copy ... FROM STDIN` input can consume following
SQL in psql echo-all regression scripts.

- replace the three affected `failure_copy_to_reference` inputs and one
affected `failure_copy_on_hash` input unconditionally with zero-row
client-side `FROM PROGRAM 'true'`
- keep the server-side Citus failure injection, errors, placement
checks, and row-count assertions unchanged
- update only the four corresponding echoed commands in the canonical
expected files
- use no version gate, alternative expected output, harness change,
normalization, or global machinery

`\copy ... FROM PROGRAM` is client-side and works consistently across
the supported clients tested here, while supplying no rows for failures
that occur before COPY_IN.

## Validation

Exact commit/tree validated: `4ee87f66cf0bfbff7447df0e5da9dc24e9f30fd1`
/ `aef35426123a65d79cda324a583736fab67b1cf8`.

Focused affected schedule:

- PG19 Beta3: 6/6
- PG19 Beta2: 6/6
- PG18.4: 6/6

Full `check-failure`:

- PG19 Beta3: 38/38 (94 seconds)
- PG19 Beta2: 38/38 (99 seconds)
- PG18.4: 38/38 (105 seconds)
- PG17.10: 38/38 (95 seconds)
- PG16.14: 38/38 (93 seconds)

PG19 Beta2 validation used the existing isolated local compatibility
shim that omits the Beta3-only `output_plugin_libraries` test setting;
the shim is not part of this PR and was restored after validation.

Final diff: 4 files, 8 insertions, 8 deletions.

Closes #8780

---------

Co-authored-by: Ibrahim Halatci <ihalatci@microsoft.com>
Copilot-Session: 809b79c3-b004-47c3-8e1d-4ba1af5b9eb5
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.

2 participants