Fix nbtree high key "continuescan" row compare bug.
authorPeter Geoghegan <pg@bowt.ie>
Mon, 1 Apr 2019 00:24:04 +0000 (17:24 -0700)
committerPeter Geoghegan <pg@bowt.ie>
Mon, 1 Apr 2019 00:24:04 +0000 (17:24 -0700)
Commit 29b64d1d mishandled skipping over truncated high key attributes
during row comparisons.  The row comparison key matching loop would loop
forever when a truncated attribute was encountered for a row compare
subkey.  Fix by following the example of other code in the loop: advance
the current subkey, or break out of the loop when the last subkey is
reached.

Add test coverage for the relevant _bt_check_rowcompare() code path.
The new test case is somewhat tied to nbtree implementation details,
which isn't ideal, but seems unavoidable.

src/backend/access/nbtree/nbtutils.c
src/test/regress/expected/index_including.out
src/test/regress/sql/index_including.sql

index 92b8b5f134d79fb09ee441e5557c2701b1062703..140ac9202659398924239308082ba771f60afde9 100644 (file)
@@ -1543,6 +1543,9 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
             */
            Assert(ScanDirectionIsForward(dir));
            cmpresult = 0;
+           if (subkey->sk_flags & SK_ROW_END)
+               break;
+           subkey++;
            continue;
        }
 
index 77ec29f2a33ef7be8410153847ccce3edaaac01e..2405709f4099c6a8949f15e0e0ccbd4df7e9dfc1 100644 (file)
@@ -126,7 +126,7 @@ DETAIL:  Key (c1, c2)=(1, 2) already exists.
 INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
 ERROR:  null value in column "c2" violates not-null constraint
 DETAIL:  Failing row contains (1, null, 3, (4,4),(4,4)).
-INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,300) AS x;
 explain (costs off)
 select * from tbl where (c1,c2,c3) < (2,5,1);
                    QUERY PLAN                   
@@ -144,7 +144,26 @@ select * from tbl where (c1,c2,c3) < (2,5,1);
   2 |  4 |    | 
 (2 rows)
 
+-- row comparison that compares high key at page boundary
+SET enable_seqscan = off;
+explain (costs off)
+select * from tbl where (c1,c2,c3) < (262,1,1) limit 1;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Limit
+   ->  Index Only Scan using covering on tbl
+         Index Cond: (ROW(c1, c2) <= ROW(262, 1))
+         Filter: (ROW(c1, c2, c3) < ROW(262, 1, 1))
+(4 rows)
+
+select * from tbl where (c1,c2,c3) < (262,1,1) limit 1;
+ c1 | c2 | c3 | c4 
+----+----+----+----
+  1 |  2 |    | 
+(1 row)
+
 DROP TABLE tbl;
+RESET enable_seqscan;
 CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
                UNIQUE(c1,c2) INCLUDE(c3,c4));
 SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
index c0ae71d0cbd798931b400967d6f86b5df915ffa2..7e517483adf49b8e691b660dac294d7a9c8b051f 100644 (file)
@@ -72,11 +72,17 @@ SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conre
 -- ensure that constraint works
 INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
 INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
-INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,300) AS x;
 explain (costs off)
 select * from tbl where (c1,c2,c3) < (2,5,1);
 select * from tbl where (c1,c2,c3) < (2,5,1);
+-- row comparison that compares high key at page boundary
+SET enable_seqscan = off;
+explain (costs off)
+select * from tbl where (c1,c2,c3) < (262,1,1) limit 1;
+select * from tbl where (c1,c2,c3) < (262,1,1) limit 1;
 DROP TABLE tbl;
+RESET enable_seqscan;
 
 CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
                UNIQUE(c1,c2) INCLUDE(c3,c4));