From: Heikki Linnakangas Date: Thu, 11 Jun 2026 09:33:48 +0000 (+0300) Subject: seg: Fix seg_out() to preserve the upper boundary's certainty indicator X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=0e1f1ed157e;p=thirdparty%2Fpostgresql.git seg: Fix seg_out() to preserve the upper boundary's certainty indicator When printing the upper boundary of a seg interval, seg_out() decided whether to emit the certainty indicator ('<', '>' or '~') by testing the upper indicator (u_ext) for '<' and '>', but mistakenly tested the lower indicator (l_ext) for '~'. This is a copy-and-paste slip from the symmetric code that prints the lower boundary a few lines above. The consequences for valid input were: * A '~' on the upper boundary was dropped on output, e.g. '1.5 .. ~2.5'::seg printed as '1.5 .. 2.5'. * When the lower boundary carried '~' but the upper boundary had no indicator, the wrong test matched and sprintf(p, "%c", seg->u_ext) wrote a NUL byte (u_ext == '\0'), which truncated the result string and silently lost the entire upper boundary, e.g. '~6.5 .. 8.5'::seg printed as '~6.5 .. '. Certainty indicators are documented to be preserved on output (they are ignored by the operators, but kept as comments), so this broke the input/output round-trip for the affected values. The bug has existed since seg was added. It went unnoticed because the existing regression tests only exercised certainty indicators on single-point segs, which are printed by a different branch of seg_out(). Add tests that place indicators on both boundaries of an interval. Author: Ewan Young Discussion: https://www.postgresql.org/message-id/CAON2xHPYeRRCEVAv8XfE18KsEsEHCiYcJ5fOsoxFuMEfpxF1=g@mail.gmail.com Backpatch-through: 14 --- diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out index cd21139b5a7..b7c3fba1597 100644 --- a/contrib/seg/expected/seg.out +++ b/contrib/seg/expected/seg.out @@ -263,7 +263,8 @@ SELECT '12.345678901234560000000000000000000000000000000000000000000000000000000 12.3457 (1 row) --- Numbers with certainty indicators +-- Numbers and ranges with certainty indicators. Certainty indicators +-- are stored and preserved on output, but ignored by operators. SELECT '~6.5'::seg AS seg; seg ------ @@ -300,6 +301,48 @@ SELECT '> 6.5'::seg AS seg; >6.5 (1 row) +SELECT '~1.5 .. 2.5'::seg AS seg; + seg +------------- + ~1.5 .. 2.5 +(1 row) + +SELECT '1.5 .. ~2.5'::seg AS seg; + seg +------------- + 1.5 .. ~2.5 +(1 row) + +SELECT '~1.5 .. ~2.5'::seg AS seg; + seg +-------------- + ~1.5 .. ~2.5 +(1 row) + +SELECT '<1.5 .. 2.5'::seg AS seg; + seg +------------- + <1.5 .. 2.5 +(1 row) + +SELECT '1.5 .. <2.5'::seg AS seg; + seg +------------- + 1.5 .. <2.5 +(1 row) + +SELECT '>1.5 .. 2.5'::seg AS seg; + seg +------------- + >1.5 .. 2.5 +(1 row) + +SELECT '1.5 .. >2.5'::seg AS seg; + seg +------------- + 1.5 .. >2.5 +(1 row) + -- Open intervals SELECT '0..'::seg AS seg; seg diff --git a/contrib/seg/seg.c b/contrib/seg/seg.c index fcded0245aa..c7b374825f8 100644 --- a/contrib/seg/seg.c +++ b/contrib/seg/seg.c @@ -152,7 +152,7 @@ seg_out(PG_FUNCTION_ARGS) { /* print the upper boundary if exists */ p += sprintf(p, " "); - if (seg->u_ext == '>' || seg->u_ext == '<' || seg->l_ext == '~') + if (seg->u_ext == '>' || seg->u_ext == '<' || seg->u_ext == '~') p += sprintf(p, "%c", seg->u_ext); p += restore(p, seg->upper, seg->u_sigd); } diff --git a/contrib/seg/sql/seg.sql b/contrib/seg/sql/seg.sql index c30f1f6bef1..a74a42f7e3e 100644 --- a/contrib/seg/sql/seg.sql +++ b/contrib/seg/sql/seg.sql @@ -63,7 +63,8 @@ SELECT '12.34567890123456'::seg AS seg; -- Same, with a very long input SELECT '12.3456789012345600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'::seg AS seg; --- Numbers with certainty indicators +-- Numbers and ranges with certainty indicators. Certainty indicators +-- are stored and preserved on output, but ignored by operators. SELECT '~6.5'::seg AS seg; SELECT '<6.5'::seg AS seg; SELECT '>6.5'::seg AS seg; @@ -71,6 +72,14 @@ SELECT '~ 6.5'::seg AS seg; SELECT '< 6.5'::seg AS seg; SELECT '> 6.5'::seg AS seg; +SELECT '~1.5 .. 2.5'::seg AS seg; +SELECT '1.5 .. ~2.5'::seg AS seg; +SELECT '~1.5 .. ~2.5'::seg AS seg; +SELECT '<1.5 .. 2.5'::seg AS seg; +SELECT '1.5 .. <2.5'::seg AS seg; +SELECT '>1.5 .. 2.5'::seg AS seg; +SELECT '1.5 .. >2.5'::seg AS seg; + -- Open intervals SELECT '0..'::seg AS seg; SELECT '0...'::seg AS seg;