Skip to content

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

Open
Alena0704 wants to merge 2 commits into
apache:REL_2_STABLEfrom
Alena0704:gp-percentile-rel2
Open

Fix two crashes/wrong-answer bugs in the gp_percentile_* transition functions (in REL_2_STABLE)#1935
Alena0704 wants to merge 2 commits into
apache:REL_2_STABLEfrom
Alena0704:gp-percentile-rel2

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. Direct SQL calls read past the end of the argument array (2f409c8)

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.

Fixing the declaration would force an initdb, which is not acceptable on a
stable branch, so the argument count is checked and a plain error is
raised. Direct calls were never useful.

  1. The isnull flag is lost when the previous state is returned (8251c47)

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.

Both ported from Greengage 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);
-- before: server closed the connection unexpectedly
-- after:  ERROR:  wrong number of arguments to gp_percentile_disc_transition()
--         HINT:  expected 5, got 4

-- 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


@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, we should just fix it )

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, declares them with
four.  As transition functions they are called correctly, 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.

Correcting the declaration would change the catalog and force an initdb, which
is not acceptable on a stable branch, so check the argument count instead and
raise a plain error.  Direct calls were never useful - the functions only make
sense as the transition step of their aggregates - and 'percentile_* WITHIN
GROUP' queries are unaffected either way.

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

Ported from Greengage/open-gpdb 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 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Fixed commit messages a bit.

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