mirror of
https://github.com/postgres/postgres.git
synced 2026-06-13 18:50:17 -04:00
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
249 lines
7.4 KiB
SQL
249 lines
7.4 KiB
SQL
--
|
|
-- Test seg datatype
|
|
--
|
|
|
|
CREATE EXTENSION seg;
|
|
|
|
-- Check whether any of our opclasses fail amvalidate
|
|
SELECT amname, opcname
|
|
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
|
|
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
|
|
|
|
--
|
|
-- testing the input and output functions
|
|
--
|
|
|
|
-- Any number
|
|
SELECT '1'::seg AS seg;
|
|
SELECT '-1'::seg AS seg;
|
|
SELECT '1.0'::seg AS seg;
|
|
SELECT '-1.0'::seg AS seg;
|
|
SELECT '1e7'::seg AS seg;
|
|
SELECT '-1e7'::seg AS seg;
|
|
SELECT '1.0e7'::seg AS seg;
|
|
SELECT '-1.0e7'::seg AS seg;
|
|
SELECT '1e+7'::seg AS seg;
|
|
SELECT '-1e+7'::seg AS seg;
|
|
SELECT '1.0e+7'::seg AS seg;
|
|
SELECT '-1.0e+7'::seg AS seg;
|
|
SELECT '1e-7'::seg AS seg;
|
|
SELECT '-1e-7'::seg AS seg;
|
|
SELECT '1.0e-7'::seg AS seg;
|
|
SELECT '-1.0e-7'::seg AS seg;
|
|
SELECT '2e-6'::seg AS seg;
|
|
SELECT '2e-5'::seg AS seg;
|
|
SELECT '2e-4'::seg AS seg;
|
|
SELECT '2e-3'::seg AS seg;
|
|
SELECT '2e-2'::seg AS seg;
|
|
SELECT '2e-1'::seg AS seg;
|
|
SELECT '2e-0'::seg AS seg;
|
|
SELECT '2e+0'::seg AS seg;
|
|
SELECT '2e+1'::seg AS seg;
|
|
SELECT '2e+2'::seg AS seg;
|
|
SELECT '2e+3'::seg AS seg;
|
|
SELECT '2e+4'::seg AS seg;
|
|
SELECT '2e+5'::seg AS seg;
|
|
SELECT '2e+6'::seg AS seg;
|
|
|
|
|
|
-- Significant digits preserved
|
|
SELECT '1'::seg AS seg;
|
|
SELECT '1.0'::seg AS seg;
|
|
SELECT '1.00'::seg AS seg;
|
|
SELECT '1.000'::seg AS seg;
|
|
SELECT '1.0000'::seg AS seg;
|
|
SELECT '1.00000'::seg AS seg;
|
|
SELECT '1.000000'::seg AS seg;
|
|
SELECT '0.000000120'::seg AS seg;
|
|
SELECT '3.400e5'::seg AS seg;
|
|
|
|
-- Digits truncated
|
|
SELECT '12.34567890123456'::seg AS seg;
|
|
|
|
-- Same, with a very long input
|
|
SELECT '12.3456789012345600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'::seg AS seg;
|
|
|
|
-- 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;
|
|
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;
|
|
SELECT '0 ..'::seg AS seg;
|
|
SELECT '0 ...'::seg AS seg;
|
|
SELECT '..0'::seg AS seg;
|
|
SELECT '...0'::seg AS seg;
|
|
SELECT '.. 0'::seg AS seg;
|
|
SELECT '... 0'::seg AS seg;
|
|
|
|
-- Finite intervals
|
|
SELECT '0 .. 1'::seg AS seg;
|
|
SELECT '-1 .. 0'::seg AS seg;
|
|
SELECT '-1 .. 1'::seg AS seg;
|
|
|
|
-- (+/-) intervals
|
|
SELECT '0(+-)1'::seg AS seg;
|
|
SELECT '0(+-)1.0'::seg AS seg;
|
|
SELECT '1.0(+-)0.005'::seg AS seg;
|
|
SELECT '101(+-)1'::seg AS seg;
|
|
-- incorrect number of significant digits in 99.0:
|
|
SELECT '100(+-)1'::seg AS seg;
|
|
|
|
-- invalid input
|
|
SELECT ''::seg AS seg;
|
|
SELECT 'ABC'::seg AS seg;
|
|
SELECT '1ABC'::seg AS seg;
|
|
SELECT '1.'::seg AS seg;
|
|
SELECT '1.....'::seg AS seg;
|
|
SELECT '.1'::seg AS seg;
|
|
SELECT '1..2.'::seg AS seg;
|
|
SELECT '1 e7'::seg AS seg;
|
|
SELECT '1e700'::seg AS seg;
|
|
|
|
--
|
|
-- testing the operators
|
|
--
|
|
|
|
-- equality/inequality:
|
|
--
|
|
SELECT '24 .. 33.20'::seg = '24 .. 33.20'::seg AS bool;
|
|
SELECT '24 .. 33.20'::seg = '24 .. 33.21'::seg AS bool;
|
|
SELECT '24 .. 33.20'::seg != '24 .. 33.20'::seg AS bool;
|
|
SELECT '24 .. 33.20'::seg != '24 .. 33.21'::seg AS bool;
|
|
|
|
-- overlap
|
|
--
|
|
SELECT '1'::seg && '1'::seg AS bool;
|
|
SELECT '1'::seg && '2'::seg AS bool;
|
|
SELECT '0 ..'::seg && '0 ..'::seg AS bool;
|
|
SELECT '0 .. 1'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '..0'::seg && '0..'::seg AS bool;
|
|
SELECT '-1 .. 0.1'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '-1 .. 0'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '-1 .. -0.0001'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '0 ..'::seg && '1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg && '1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg && '2'::seg AS bool;
|
|
SELECT '0 .. 2'::seg && '1'::seg AS bool;
|
|
SELECT '1'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '2'::seg && '0 .. 1'::seg AS bool;
|
|
SELECT '1'::seg && '0 .. 2'::seg AS bool;
|
|
|
|
-- overlap on the left
|
|
--
|
|
SELECT '1'::seg &< '0'::seg AS bool;
|
|
SELECT '1'::seg &< '1'::seg AS bool;
|
|
SELECT '1'::seg &< '2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '0'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '0 .. 0.5'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '0 .. 2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '1 .. 2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &< '2 .. 3'::seg AS bool;
|
|
|
|
-- overlap on the right
|
|
--
|
|
SELECT '0'::seg &> '1'::seg AS bool;
|
|
SELECT '1'::seg &> '1'::seg AS bool;
|
|
SELECT '2'::seg &> '1'::seg AS bool;
|
|
SELECT '0'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '1'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '2'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 0.5'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 2'::seg &> '0 .. 2'::seg AS bool;
|
|
SELECT '1 .. 2'::seg &> '0 .. 1'::seg AS bool;
|
|
SELECT '2 .. 3'::seg &> '0 .. 1'::seg AS bool;
|
|
|
|
-- left
|
|
--
|
|
SELECT '1'::seg << '0'::seg AS bool;
|
|
SELECT '1'::seg << '1'::seg AS bool;
|
|
SELECT '1'::seg << '2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '0'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '0 .. 0.5'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '0 .. 2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '1 .. 2'::seg AS bool;
|
|
SELECT '0 .. 1'::seg << '2 .. 3'::seg AS bool;
|
|
|
|
-- right
|
|
--
|
|
SELECT '0'::seg >> '1'::seg AS bool;
|
|
SELECT '1'::seg >> '1'::seg AS bool;
|
|
SELECT '2'::seg >> '1'::seg AS bool;
|
|
SELECT '0'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '1'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '2'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 0.5'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 1'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '0 .. 2'::seg >> '0 .. 2'::seg AS bool;
|
|
SELECT '1 .. 2'::seg >> '0 .. 1'::seg AS bool;
|
|
SELECT '2 .. 3'::seg >> '0 .. 1'::seg AS bool;
|
|
|
|
|
|
-- "contained in" (the left value belongs within the interval specified in the right value):
|
|
--
|
|
SELECT '0'::seg <@ '0'::seg AS bool;
|
|
SELECT '0'::seg <@ '0 ..'::seg AS bool;
|
|
SELECT '0'::seg <@ '.. 0'::seg AS bool;
|
|
SELECT '0'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '0'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '1'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '-1 .. 1'::seg <@ '-1 .. 1'::seg AS bool;
|
|
|
|
-- "contains" (the left value contains the interval specified in the right value):
|
|
--
|
|
SELECT '0'::seg @> '0'::seg AS bool;
|
|
SELECT '0 .. '::seg <@ '0'::seg AS bool;
|
|
SELECT '.. 0'::seg <@ '0'::seg AS bool;
|
|
SELECT '-1 .. 1'::seg <@ '0'::seg AS bool;
|
|
SELECT '0'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool;
|
|
SELECT '1'::seg <@ '-1 .. 1'::seg AS bool;
|
|
|
|
-- Load some example data and build the index
|
|
--
|
|
CREATE TABLE test_seg (s seg);
|
|
|
|
\copy test_seg from 'data/test_seg.data'
|
|
|
|
CREATE INDEX test_seg_ix ON test_seg USING gist (s);
|
|
|
|
SET enable_indexscan = false;
|
|
EXPLAIN (COSTS OFF)
|
|
SELECT count(*) FROM test_seg WHERE s @> '11..11.3';
|
|
SELECT count(*) FROM test_seg WHERE s @> '11..11.3';
|
|
RESET enable_indexscan;
|
|
|
|
SET enable_bitmapscan = false;
|
|
EXPLAIN (COSTS OFF)
|
|
SELECT count(*) FROM test_seg WHERE s @> '11..11.3';
|
|
SELECT count(*) FROM test_seg WHERE s @> '11..11.3';
|
|
RESET enable_bitmapscan;
|
|
|
|
-- Test sorting
|
|
SELECT * FROM test_seg WHERE s @> '11..11.3' GROUP BY s;
|
|
|
|
-- Test functions
|
|
SELECT seg_lower(s), seg_center(s), seg_upper(s)
|
|
FROM test_seg WHERE s @> '11.2..11.3' OR s IS NULL ORDER BY s;
|