-- only use parallelism when explicitly intending to do so SET max_parallel_maintenance_workers = 0; SET max_parallel_workers = 0; -- A table with contents that, when sorted, triggers abbreviated -- key aborts. One easy way to achieve that is to use uuids that all -- have the same prefix, as abbreviated keys for uuids just use the -- first sizeof(Datum) bytes. CREATE TEMP TABLE abbrev_abort_uuids ( id serial not null, abort_increasing uuid, abort_decreasing uuid, noabort_increasing uuid, noabort_decreasing uuid); INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing) SELECT ('00000000-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid abort_increasing, ('00000000-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid abort_decreasing, (to_char(g.i % 10009, '00000000FM')||'-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid noabort_increasing, (to_char(((20000 - g.i) % 10009), '00000000FM')||'-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid noabort_decreasing FROM generate_series(0, 20000, 1) g(i); -- and a few NULLs INSERT INTO abbrev_abort_uuids(id) VALUES(0); INSERT INTO abbrev_abort_uuids DEFAULT VALUES; INSERT INTO abbrev_abort_uuids DEFAULT VALUES; -- add just a few duplicates INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing) SELECT abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids WHERE (id < 10 OR id > 19990) AND id % 3 = 0 AND abort_increasing is not null; ---- -- Check sort node uses of tuplesort wrt. abbreviated keys ---- -- plain sort triggering abbreviated abort SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing OFFSET 20000 - 4; SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing NULLS FIRST OFFSET 20000 - 4; -- plain sort not triggering abbreviated abort SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing OFFSET 20000 - 4; SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing NULLS FIRST OFFSET 20000 - 4; -- bounded sort (disables abbreviated keys) SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY noabort_increasing NULLS FIRST LIMIT 5; ---- -- Check index creation uses of tuplesort wrt. abbreviated keys ---- -- index creation using abbreviated keys successfully CREATE INDEX abbrev_abort_uuids__noabort_increasing_idx ON abbrev_abort_uuids (noabort_increasing); CREATE INDEX abbrev_abort_uuids__noabort_decreasing_idx ON abbrev_abort_uuids (noabort_decreasing); -- verify EXPLAIN (COSTS OFF) SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5; SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5; EXPLAIN (COSTS OFF) SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5; SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5; -- index creation using abbreviated keys, hitting abort CREATE INDEX abbrev_abort_uuids__abort_increasing_idx ON abbrev_abort_uuids (abort_increasing); CREATE INDEX abbrev_abort_uuids__abort_decreasing_idx ON abbrev_abort_uuids (abort_decreasing); -- verify EXPLAIN (COSTS OFF) SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5; EXPLAIN (COSTS OFF) SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5; SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5; ---- -- Check CLUSTER uses of tuplesort wrt. abbreviated keys ---- -- when aborting, increasing order BEGIN; SET LOCAL enable_indexscan = false; CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_increasing_idx; -- head SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid LIMIT 5; -- tail SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid DESC LIMIT 5; ROLLBACK; -- when aborting, decreasing order BEGIN; SET LOCAL enable_indexscan = false; CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_decreasing_idx; -- head SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid LIMIT 5; -- tail SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid DESC LIMIT 5; ROLLBACK; -- when not aborting, increasing order BEGIN; SET LOCAL enable_indexscan = false; CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_increasing_idx; -- head SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid LIMIT 5; -- tail SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid DESC LIMIT 5; ROLLBACK; -- when no aborting, decreasing order BEGIN; SET LOCAL enable_indexscan = false; CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_decreasing_idx; -- head SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid LIMIT 5; -- tail SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY ctid DESC LIMIT 5; ROLLBACK; ---- -- test sorting of large datums VALUES ---- -- Ensure the order is correct and values look intact SELECT LEFT(a,10),b FROM (VALUES(REPEAT('a', 512 * 1024),1),(REPEAT('b', 512 * 1024),2)) v(a,b) ORDER BY v.a DESC; ---- -- test forward and backward scans for in-memory and disk based tuplesort ---- -- in-memory BEGIN; SET LOCAL enable_indexscan = false; -- unfortunately can't show analyze output confirming sort method, -- the memory used output wouldn't be stable EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; -- first and second FETCH NEXT FROM c; FETCH NEXT FROM c; -- scroll beyond beginning FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; -- scroll beyond end FETCH LAST FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; FETCH NEXT FROM c; FETCH NEXT FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; COMMIT; -- disk based BEGIN; SET LOCAL enable_indexscan = false; SET LOCAL work_mem = '100kB'; -- unfortunately can't show analyze output confirming sort method, -- the memory used output wouldn't be stable EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing; -- first and second FETCH NEXT FROM c; FETCH NEXT FROM c; -- scroll beyond beginning FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; -- scroll beyond end FETCH LAST FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; FETCH NEXT FROM c; FETCH NEXT FROM c; FETCH BACKWARD FROM c; FETCH NEXT FROM c; COMMIT; ---- -- test tuplesort using both in-memory and disk sort --- -- memory based SELECT -- fixed-width by-value datum (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5], -- fixed-width by-ref datum (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5], -- variable-width datum (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5], -- fixed width by-value datum tuplesort percentile_disc(0.99) WITHIN GROUP (ORDER BY id), -- ensure state is shared percentile_disc(0.01) WITHIN GROUP (ORDER BY id), -- fixed width by-ref datum tuplesort percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing), -- variable width by-ref datum tuplesort percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text), -- multi-column tuplesort rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text) FROM ( SELECT * FROM abbrev_abort_uuids UNION ALL SELECT NULL, NULL, NULL, NULL, NULL) s; -- disk based (see also above) BEGIN; SET LOCAL work_mem = '100kB'; SELECT (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5], (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5], (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5], percentile_disc(0.99) WITHIN GROUP (ORDER BY id), percentile_disc(0.01) WITHIN GROUP (ORDER BY id), percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing), percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text), rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text) FROM ( SELECT * FROM abbrev_abort_uuids UNION ALL SELECT NULL, NULL, NULL, NULL, NULL) s; ROLLBACK; ---- -- test tuplesort mark/restore --- CREATE TEMP TABLE test_mark_restore(col1 int, col2 int, col12 int); -- need a few duplicates for mark/restore to matter INSERT INTO test_mark_restore(col1, col2, col12) SELECT a.i, b.i, a.i * b.i FROM generate_series(1, 500) a(i), generate_series(1, 5) b(i); BEGIN; SET LOCAL enable_nestloop = off; SET LOCAL enable_hashjoin = off; SET LOCAL enable_material = off; -- set query into variable once, to avoid repetition of the fairly long query SELECT $$ SELECT col12, count(distinct a.col1), count(distinct a.col2), count(distinct b.col1), count(distinct b.col2), count(*) FROM test_mark_restore a JOIN test_mark_restore b USING(col12) GROUP BY 1 HAVING count(*) > 1 ORDER BY 2 DESC, 1 DESC, 3 DESC, 4 DESC, 5 DESC, 6 DESC LIMIT 10 $$ AS qry \gset -- test mark/restore with in-memory sorts EXPLAIN (COSTS OFF) :qry; :qry; -- test mark/restore with on-disk sorts SET LOCAL work_mem = '100kB'; EXPLAIN (COSTS OFF) :qry; :qry; COMMIT;