postgres.git / summary / log / commit / refs
commit b3aa2083a4dabdcfa4b240f956c49784ec0fb34d
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu Jun 11 09:33:48 2026 +0000
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 <kdbase.hack@gmail.com>
Discussion: https://www.postgresql.org/message-id/CAON2xHPYeRRCEVAv8XfE18KsEsEHCiYcJ5fOsoxFuMEfpxF1=g@mail.gmail.com
Backpatch-through: 14
contrib/seg/expected/seg.out | 45 +++++++++++++++++++++++++++++++++++++++++++-
contrib/seg/seg.c | 2 +-
contrib/seg/sql/seg.sql | 11 ++++++++++-
3 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out
index 2320464dd47..9dbcf35e071 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 91b8a796004..1f665051abf 100644
--- a/contrib/seg/seg.c
+++ b/contrib/seg/seg.c
@@ -147,7 +147,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 a027d4de97e..0081854c016 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;
[parent: bc5775149f59]