Skip to content

Fix two crashes/wrong-answer bugs in the gp_percentile_* transition functions (in main) - #1937

Open
Alena0704 wants to merge 2 commits into
apache:mainfrom
Alena0704:gp-percentile-agg-ext
Open

Fix two crashes/wrong-answer bugs in the gp_percentile_* transition functions (in main)#1937
Alena0704 wants to merge 2 commits into
apache:mainfrom
Alena0704:gp-percentile-agg-ext

Conversation

@Alena0704

@Alena0704 Alena0704 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

gp_percentile_cont() / gp_percentile_disc() are the split ordered-set
aggregates ORCA rewrites percentile_cont(), percentile_disc() and median()
into. Two bugs in their transition functions. Only the ORCA path reaches
these aggregates on its own, so percentile_* WITHIN GROUP on the Postgres
planner is unaffected.

  1. The catalog declares the wrong number of arguments

The transition functions read five arguments in C (state + the four
aggregate arguments), but pg_proc.dat declares four. As transition
functions they are called correctly; a direct SQL call makes
PG_GETARG_INT64(4) pick up garbage — wrong results, an assertion on
pfree(NULL), or a segfault.

  1. The isnull flag is lost when the previous state is returned

When the current row isn't one the percentile is computed from, the
previous state is handed back untouched. On the first call that state is
NULL, and returning a bare Datum(0) with isnull false drops the flag: 0
instead of NULL for by-value types, a NULL pointer dereference for
by-reference ones — an empty input set crashed the backend for interval,
timestamp and timestamptz.

Co-authored-by: Georgy Shelkovy g.shelkovy@arenadata.io

Both ported from Greengage commit 477b04a (ADBDEV-7770).

The bug reproduction:

-- Bug 1: pg_proc.dat declares four arguments, the code reads five
select gp_percentile_disc_transition(NULL::numeric, 1, 1, 1);  -- crash
-- after the fix the five-argument form resolves and works:
select gp_percentile_disc_transition(NULL::numeric, 1::numeric, 1, 1, 1);  -- 1

-- Bug 2, by-value: silently wrong answer
select gp_percentile_cont(0::float8, 0, 0, 0);             -- 0, expecting NULL
-- Bug 2, by-reference: backend crash
select gp_percentile_cont('0 hour'::interval, 0, 0, 0);    -- server closed the connection
-- Bug 2, the 6c289ad regression: a selected value whose Datum is 0 turns into NULL
select gp_percentile_disc(0::float8, 0, 1, 1);             -- NULL, expecting 0
select gp_percentile_disc(0::int,    0, 1, 1);             -- NULL, expecting 0

-- The same, end to end: ORCA and the Postgres planner disagree
create table perczero (a int, b float8) distributed by (a);
insert into perczero select i, (i - 1)::float8 from generate_series(1, 10) i;
select percentile_disc(0) within group (order by b) from perczero;  -- NULL
set optimizer = off;
select percentile_disc(0) within group (order by b) from perczero;  -- 0

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance:

User-facing changes:

Dependencies:

Checklist

Additional Context

CI Skip Instructions


@Alena0704 Alena0704 changed the title Gp percentile agg ext Fix two crashes/wrong-answer bugs in the gp_percentile_* transition functions (in main) Aug 27, 2026

@leborchuk leborchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, let's fix the catalog. It's a good time to do it, not to leave wrong declaration in a catalog as we forced to do for REL_2_STABLE

gp_percentile_cont_{float8,interval,timestamp,timestamptz}_transition and
gp_percentile_disc_transition all read five arguments in C: the running
transition state plus the four arguments of the gp_percentile_cont() and
gp_percentile_disc() aggregates.  pg_proc.dat, however, declared them with
four.  Called as a transition function that is harmless, since the executor
supplies state + 4 arguments regardless of the catalog, but a direct SQL call
reaches past the end of the argument array: PG_GETARG_INT64(4) picks up
garbage, which yields wrong results, an assertion when the bogus peer count
makes the code pfree() a NULL pointer, or a segfault.

Co-authored-by: Georgy Shelkovy <g.shelkovy@arenadata.io>

Ported from Greengage commit 477b04a (ADBDEV-7770).
Both gp_percentile transition functions return the previous transition state
untouched when the row they are looking at is not one of the rows the
percentile is computed from.  On the very first call that state is NULL, and
returning it as a bare Datum(0) with isnull left false loses the flag: the
aggregate yields 0 instead of NULL for by-value types, and dereferences a NULL
pointer in the output function for by-reference ones - so an empty input set
crashed the backend for the interval, timestamp and timestamptz variants.

Co-authored-by: Georgy Shelkovy <g.shelkovy@arenadata.io>

Ported from Greengage/open-gpdb commit 477b04a (ADBDEV-7770).
@Alena0704
Alena0704 force-pushed the gp-percentile-agg-ext branch from ea7ddf6 to 0415f27 Compare August 28, 2026 10:37
@Alena0704

Copy link
Copy Markdown
Contributor Author

Fixed tests and corrected commit messages

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