Fix CLUSTER tuplesorts on abbreviated expressions.
authorPeter Geoghegan
Thu, 21 Apr 2022 00:17:39 +0000 (17:17 -0700)
committerPeter Geoghegan
Thu, 21 Apr 2022 00:17:39 +0000 (17:17 -0700)
CLUSTER sort won't use the datum1 SortTuple field when clustering
against an index whose leading key is an expression.  This makes it
unsafe to use the abbreviated keys optimization, which was missed by the
logic that sets up SortSupport state.  Affected tuplesorts output tuples
in a completely bogus order as a result (the wrong SortSupport based
comparator was used for the leading attribute).

This issue is similar to the bug fixed on the master branch by recent
commit cc58eecc5d.  But it's a far older issue, that dates back to the
introduction of the abbreviated keys optimization by commit 4ea51cdfe8.

Backpatch to all supported versions.

Author: Peter Geoghegan 
Author: Thomas Munro 
Discussion: https://postgr.es/m/CA+hUKG+bA+bmwD36_oDxAoLrCwZjVtST2fqe=b4=qZcmU7u89A@mail.gmail.com
Backpatch: 10-

src/backend/utils/sort/tuplesort.c
src/test/regress/expected/cluster.out
src/test/regress/sql/cluster.sql

index 98d68a143d97a941bb9ac43b27739d8c04e8d9e5..22f1bd872beaf3503894302732c703f6da04e4e6 100644 (file)
@@ -956,6 +956,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
 {
    Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate,
                                                   randomAccess);
+   AttrNumber  leading;
    BTScanInsert indexScanKey;
    MemoryContext oldcontext;
    int         i;
@@ -988,6 +989,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
    state->abbrevNext = 10;
 
    state->indexInfo = BuildIndexInfo(indexRel);
+   leading = state->indexInfo->ii_IndexAttrNumbers[0];
 
    state->tupDesc = tupDesc;   /* assume we need not copy tupDesc */
 
@@ -1026,7 +1028,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
            (scanKey->sk_flags & SK_BT_NULLS_FIRST) != 0;
        sortKey->ssup_attno = scanKey->sk_attno;
        /* Convey if abbreviation optimization is applicable in principle */
-       sortKey->abbreviate = (i == 0);
+       sortKey->abbreviate = (i == 0 && leading != 0);
 
        AssertState(sortKey->ssup_attno != 0);
 
index e46a66952f0c408c98c67f4096df499653b92b1d..7293c0f03ea7aac11ff93ef1a4d72ed0d7d74405 100644 (file)
@@ -511,6 +511,13 @@ SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
 COMMIT;
 -- and after clustering on clstr_expression_minus_a
 CLUSTER clstr_expression USING clstr_expression_minus_a;
+WITH rows AS
+  (SELECT ctid, lag(a) OVER (ORDER BY ctid) AS la, a FROM clstr_expression)
+SELECT * FROM rows WHERE la < a;
+ ctid | la | a 
+------+----+---
+(0 rows)
+
 BEGIN;
 SET LOCAL enable_seqscan = false;
 EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
@@ -545,6 +552,13 @@ SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
 COMMIT;
 -- and after clustering on clstr_expression_upper_b
 CLUSTER clstr_expression USING clstr_expression_upper_b;
+WITH rows AS
+  (SELECT ctid, lag(b) OVER (ORDER BY ctid) AS lb, b FROM clstr_expression)
+SELECT * FROM rows WHERE upper(lb) > upper(b);
+ ctid | lb | b 
+------+----+---
+(0 rows)
+
 BEGIN;
 SET LOCAL enable_seqscan = false;
 EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
index aee9cf83e04c8433d2d300081baa481a12d06fa7..99ee533c8d00e5c583a655ef35dedbba5190c373 100644 (file)
@@ -245,6 +245,9 @@ COMMIT;
 
 -- and after clustering on clstr_expression_minus_a
 CLUSTER clstr_expression USING clstr_expression_minus_a;
+WITH rows AS
+  (SELECT ctid, lag(a) OVER (ORDER BY ctid) AS la, a FROM clstr_expression)
+SELECT * FROM rows WHERE la < a;
 BEGIN;
 SET LOCAL enable_seqscan = false;
 EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
@@ -255,6 +258,9 @@ COMMIT;
 
 -- and after clustering on clstr_expression_upper_b
 CLUSTER clstr_expression USING clstr_expression_upper_b;
+WITH rows AS
+  (SELECT ctid, lag(b) OVER (ORDER BY ctid) AS lb, b FROM clstr_expression)
+SELECT * FROM rows WHERE upper(lb) > upper(b);
 BEGIN;
 SET LOCAL enable_seqscan = false;
 EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';