From 14873adac1d42d43c708f2a4d40ef44bafeada76 Mon Sep 17 00:00:00 2001 From: Andrei Lepikhov Date: Tue, 3 Nov 2026 14:39:18 +0100 Subject: [PATCH v0 2/2] Add sort-ahead paths for base relations. If a base relation can be sorted early on a prefix of query_pathkeys, add an explicit Sort over its cheapest unparameterized path to its pathlist in set_rel_pathlist(). This gives a relation without a suitable index what such an index would provide: an ordering that order-preserving joins, Merge Append, sort-based unique-ification and grouping can use, so the final ordering may be delivered without a Sort above the joins. The path competes with others in add_path(), is costed as a full sort, and is not generated for a single-relation query. --- contrib/pg_plan_advice/expected/gather.out | 52 +-- .../pg_plan_advice/expected/join_strategy.out | 49 +++ contrib/pg_plan_advice/sql/join_strategy.sql | 13 + .../postgres_fdw/expected/postgres_fdw.out | 137 ++++---- src/backend/optimizer/path/allpaths.c | 53 +++ .../regress/expected/collate.icu.utf8.out | 22 +- src/test/regress/expected/create_am.out | 8 +- src/test/regress/expected/eager_aggregate.out | 24 +- .../regress/expected/incremental_sort.out | 45 +-- src/test/regress/expected/inherit.out | 12 +- src/test/regress/expected/join.out | 295 +++++++++++------ src/test/regress/expected/partition_join.out | 311 ++++++++++-------- src/test/regress/expected/subselect.out | 7 +- src/test/regress/expected/tablesample.out | 8 +- src/test/regress/expected/tuplesort.out | 42 +-- src/test/regress/sql/create_am.sql | 8 +- src/test/regress/sql/incremental_sort.sql | 7 +- src/test/regress/sql/join.sql | 38 +++ 18 files changed, 723 insertions(+), 408 deletions(-) diff --git a/contrib/pg_plan_advice/expected/gather.out b/contrib/pg_plan_advice/expected/gather.out index 60f2b32e24f..3577ed5b2fe 100644 --- a/contrib/pg_plan_advice/expected/gather.out +++ b/contrib/pg_plan_advice/expected/gather.out @@ -161,17 +161,18 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE) -> Sort Sort Key: f.dim_id -> Parallel Seq Scan on gt_fact f - -> Index Scan using gt_dim_pkey on gt_dim d + -> Sort + Sort Key: d.id + -> Seq Scan on gt_dim d Supplied Plan Advice: GATHER((d d/d.d)) /* partially matched */ Generated Plan Advice: JOIN_ORDER(f d) MERGE_JOIN_PLAIN(d) - SEQ_SCAN(f) - INDEX_SCAN(d public.gt_dim_pkey) + SEQ_SCAN(f d) GATHER_MERGE(f) NO_GATHER(d) -(17 rows) +(18 rows) COMMIT; -- Force a Gather or Gather Merge on one relation but no parallelism on other. @@ -188,18 +189,19 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE) -> Sort Sort Key: f.dim_id -> Parallel Seq Scan on gt_fact f - -> Index Scan using gt_dim_pkey on gt_dim d + -> Sort + Sort Key: d.id + -> Seq Scan on gt_dim d Supplied Plan Advice: GATHER_MERGE(f) /* matched */ NO_GATHER(d) /* matched */ Generated Plan Advice: JOIN_ORDER(f d) MERGE_JOIN_PLAIN(d) - SEQ_SCAN(f) - INDEX_SCAN(d public.gt_dim_pkey) + SEQ_SCAN(f d) GATHER_MERGE(f) NO_GATHER(d) -(18 rows) +(19 rows) SET LOCAL pg_plan_advice.advice = 'gather_merge(d) no_gather(f)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) @@ -233,24 +235,25 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE) QUERY PLAN -------------------------------------------------- Merge Join - Merge Cond: (d.id = f.dim_id) - -> Index Scan using gt_dim_pkey on gt_dim d + Merge Cond: (f.dim_id = d.id) -> Sort Sort Key: f.dim_id -> Gather Workers Planned: 1 -> Parallel Seq Scan on gt_fact f + -> Sort + Sort Key: d.id + -> Seq Scan on gt_dim d Supplied Plan Advice: GATHER(f) /* matched */ NO_GATHER(d) /* matched */ Generated Plan Advice: - JOIN_ORDER(d f) - MERGE_JOIN_PLAIN(f) - SEQ_SCAN(f) - INDEX_SCAN(d public.gt_dim_pkey) + JOIN_ORDER(f d) + MERGE_JOIN_PLAIN(d) + SEQ_SCAN(f d) GATHER(f) NO_GATHER(d) -(18 rows) +(19 rows) SET LOCAL pg_plan_advice.advice = 'gather(d) no_gather(f)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) @@ -284,24 +287,25 @@ BEGIN; SET LOCAL pg_plan_advice.advice = 'no_gather(f d)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM gt_fact f JOIN gt_dim d ON f.dim_id = d.id ORDER BY d.id; - QUERY PLAN ------------------------------------------------- + QUERY PLAN +----------------------------------- Merge Join - Merge Cond: (d.id = f.dim_id) - -> Index Scan using gt_dim_pkey on gt_dim d + Merge Cond: (f.dim_id = d.id) -> Sort Sort Key: f.dim_id -> Seq Scan on gt_fact f + -> Sort + Sort Key: d.id + -> Seq Scan on gt_dim d Supplied Plan Advice: NO_GATHER(f) /* matched */ NO_GATHER(d) /* matched */ Generated Plan Advice: - JOIN_ORDER(d f) - MERGE_JOIN_PLAIN(f) - SEQ_SCAN(f) - INDEX_SCAN(d public.gt_dim_pkey) + JOIN_ORDER(f d) + MERGE_JOIN_PLAIN(d) + SEQ_SCAN(f d) NO_GATHER(f d) -(15 rows) +(16 rows) COMMIT; -- Can't force Gather Merge without the ORDER BY clause, but just Gather is OK. diff --git a/contrib/pg_plan_advice/expected/join_strategy.out b/contrib/pg_plan_advice/expected/join_strategy.out index 0f9db692190..be9913bd1a4 100644 --- a/contrib/pg_plan_advice/expected/join_strategy.out +++ b/contrib/pg_plan_advice/expected/join_strategy.out @@ -337,3 +337,52 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE) (14 rows) COMMIT; +-- The planner may sort the outer side of a nested loop to deliver the +-- ORDER BY. That Sort is not part of the join strategy: advice must still +-- be generated for such a plan, and must be applicable back to it. +EXPLAIN (COSTS OFF, PLAN_ADVICE) + SELECT * FROM join_dim d JOIN join_fact f ON f.dim_id = d.id + ORDER BY d.dim LIMIT 10; + QUERY PLAN +-------------------------------------------------------------- + Limit + -> Nested Loop + -> Sort + Sort Key: d.dim + -> Seq Scan on join_dim d + -> Index Scan using join_fact_dim_id on join_fact f + Index Cond: (dim_id = d.id) + Generated Plan Advice: + JOIN_ORDER(d f) + NESTED_LOOP_PLAIN(f) + SEQ_SCAN(d) + INDEX_SCAN(f public.join_fact_dim_id) + NO_GATHER(d f) +(13 rows) + +BEGIN; +SET LOCAL pg_plan_advice.advice = 'JOIN_ORDER(d f) NESTED_LOOP_PLAIN(f)'; +EXPLAIN (COSTS OFF, PLAN_ADVICE) + SELECT * FROM join_dim d JOIN join_fact f ON f.dim_id = d.id + ORDER BY d.dim LIMIT 10; + QUERY PLAN +-------------------------------------------------------------- + Limit + -> Nested Loop + -> Sort + Sort Key: d.dim + -> Seq Scan on join_dim d + -> Index Scan using join_fact_dim_id on join_fact f + Index Cond: (dim_id = d.id) + Supplied Plan Advice: + JOIN_ORDER(d f) /* matched */ + NESTED_LOOP_PLAIN(f) /* matched */ + Generated Plan Advice: + JOIN_ORDER(d f) + NESTED_LOOP_PLAIN(f) + SEQ_SCAN(d) + INDEX_SCAN(f public.join_fact_dim_id) + NO_GATHER(d f) +(16 rows) + +COMMIT; diff --git a/contrib/pg_plan_advice/sql/join_strategy.sql b/contrib/pg_plan_advice/sql/join_strategy.sql index edd5c4c0e14..57f54e79f7d 100644 --- a/contrib/pg_plan_advice/sql/join_strategy.sql +++ b/contrib/pg_plan_advice/sql/join_strategy.sql @@ -82,3 +82,16 @@ SET LOCAL pg_plan_advice.advice = 'NESTED_LOOP_PLAIN(f d)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id; COMMIT; + +-- The planner may sort the outer side of a nested loop to deliver the +-- ORDER BY. That Sort is not part of the join strategy: advice must still +-- be generated for such a plan, and must be applicable back to it. +EXPLAIN (COSTS OFF, PLAN_ADVICE) + SELECT * FROM join_dim d JOIN join_fact f ON f.dim_id = d.id + ORDER BY d.dim LIMIT 10; +BEGIN; +SET LOCAL pg_plan_advice.advice = 'JOIN_ORDER(d f) NESTED_LOOP_PLAIN(f)'; +EXPLAIN (COSTS OFF, PLAN_ADVICE) + SELECT * FROM join_dim d JOIN join_fact f ON f.dim_id = d.id + ORDER BY d.dim LIMIT 10; +COMMIT; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 115ebc7925f..d37133fe5e9 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -2295,21 +2295,21 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1. ------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 - -> Sort + -> Incremental Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 - -> Merge Left Join + Presorted Key: t1.c1 + -> Nested Loop Left Join Output: t1.c1, t2.c1 - Merge Cond: (t1.c8 = t2.c8) + Join Filter: (t1.c8 = t2.c8) -> Sort Output: t1.c1, t1.c8 - Sort Key: t1.c8 + Sort Key: t1.c1 -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c8 Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" - -> Sort + -> Materialize Output: t2.c1, t2.c8 - Sort Key: t2.c8 -> Foreign Scan on public.ft2 t2 Output: t2.c1, t2.c8 Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" @@ -4514,24 +4514,24 @@ select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; set enable_hashagg to false; explain (verbose, costs off) select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; - QUERY PLAN ------------------------------------------------------------------------------------------------------- - Sort + QUERY PLAN +------------------------------------------------------------------------------------------------ + Nested Loop Output: t1.c2, qry.sum - Sort Key: t1.c2 - -> Nested Loop - Output: t1.c2, qry.sum + -> Sort + Output: t1.c2, t1."C 1" + Sort Key: t1.c2 -> Index Scan using t1_pkey on "S 1"."T 1" t1 - Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Output: t1.c2, t1."C 1" Index Cond: (t1."C 1" < 100) Filter: (t1.c2 < 3) - -> Subquery Scan on qry - Output: qry.sum, t2.c1 - Filter: ((t1.c2 * 2) = qry.sum) - -> Foreign Scan - Output: (sum((t2.c1 + t1."C 1"))), t2.c1 - Relations: Aggregate on (public.ft2 t2) - Remote SQL: SELECT sum(("C 1" + $1::integer)), "C 1" FROM "S 1"."T 1" GROUP BY 2 + -> Subquery Scan on qry + Output: qry.sum, t2.c1 + Filter: ((t1.c2 * 2) = qry.sum) + -> Foreign Scan + Output: (sum((t2.c1 + t1."C 1"))), t2.c1 + Relations: Aggregate on (public.ft2 t2) + Remote SQL: SELECT sum(("C 1" + $1::integer)), "C 1" FROM "S 1"."T 1" GROUP BY 2 (16 rows) select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; @@ -7095,26 +7095,23 @@ BEGIN; -> Nested Loop Output: ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3 Join Filter: (ft4.c1 = ft5.c1) - -> Sort + -> Hash Join Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3 - Sort Key: ft2.c2 - -> Hash Join - Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3 - Hash Cond: (ft2.c2 = ft4.c1) - -> Foreign Scan on public.ft2 - Output: ft2.ctid, ft2.*, ft2.c2 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE - -> Hash + Hash Cond: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.*, ft2.c2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE + -> Hash + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + -> Foreign Scan on public.ft4 Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 - -> Foreign Scan on public.ft4 - Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 - Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" -> Materialize Output: ft5.*, ft5.c1 -> Foreign Scan on public.ft5 Output: ft5.*, ft5.c1 Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" -(29 rows) +(26 rows) UPDATE ft2 SET c3 = 'bar' FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) @@ -11291,13 +11288,15 @@ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a -> Nested Loop Join Filter: (t1.a = t2.b) -> Append - -> Foreign Scan on ftprt1_p1 t1_1 + -> Sort + Sort Key: t1_1.a + -> Foreign Scan on ftprt1_p1 t1_1 -> Foreign Scan on ftprt1_p2 t1_2 -> Materialize -> Append -> Foreign Scan on ftprt2_p1 t2_1 -> Foreign Scan on ftprt2_p2 t2_2 -(10 rows) +(12 rows) SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; a | b @@ -12412,27 +12411,27 @@ DELETE FROM result_tbl; -- (case where subplans are re-scanned with parameter changes) EXPLAIN (VERBOSE, COSTS OFF) SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR a = 1505 LIMIT 1) s ORDER BY o.x; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------- - Sort + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop Output: "*VALUES*".column1 - Sort Key: "*VALUES*".column1 - -> Nested Loop + -> Sort Output: "*VALUES*".column1 + Sort Key: "*VALUES*".column1 -> Values Scan on "*VALUES*" Output: "*VALUES*".column1 - -> Limit - Output: NULL::integer - -> Append - -> Async Foreign Scan on public.async_p1 async_pt_1 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR (a = 1505))) - -> Async Foreign Scan on public.async_p2 async_pt_2 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR (a = 1505))) - -> Async Foreign Scan on public.async_p3 async_pt_3 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR (a = 1505))) + -> Limit + Output: NULL::integer + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR (a = 1505))) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR (a = 1505))) + -> Async Foreign Scan on public.async_p3 async_pt_3 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR (a = 1505))) (19 rows) SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR a = 1505 LIMIT 1) s ORDER BY o.x; @@ -12444,27 +12443,27 @@ SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WH EXPLAIN (VERBOSE, COSTS OFF) SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR (a = 1505 AND o.x = 2505) LIMIT 1) s ORDER BY o.x; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- - Sort + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Output: "*VALUES*".column1 - Sort Key: "*VALUES*".column1 - -> Nested Loop + -> Sort Output: "*VALUES*".column1 + Sort Key: "*VALUES*".column1 -> Values Scan on "*VALUES*" Output: "*VALUES*".column1 - -> Limit - Output: NULL::integer - -> Append - -> Async Foreign Scan on public.async_p1 async_pt_1 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) - -> Async Foreign Scan on public.async_p2 async_pt_2 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) - -> Async Foreign Scan on public.async_p3 async_pt_3 - Output: NULL::integer - Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) + -> Limit + Output: NULL::integer + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) + -> Async Foreign Scan on public.async_p3 async_pt_3 + Output: NULL::integer + Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505)))) (19 rows) SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR (a = 1505 AND o.x = 2505) LIMIT 1) s ORDER BY o.x; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 72ad61e8c24..798bee97429 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -103,6 +103,10 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel); +static List *get_useful_pathkeys_for_relation(PlannerInfo *root, + RelOptInfo *rel, + bool require_parallel_safe); +static void generate_presorted_paths(PlannerInfo *root, RelOptInfo *rel); static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -607,6 +611,13 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, !bms_equal(rel->relids, root->all_query_rels)) generate_useful_gather_paths(root, rel, false); + /* + * Offer an explicitly sorted path on a prefix of query_pathkeys. With a + * single base relation there is no join to push the sort below. + */ + if (bms_membership(root->all_baserels) == BMS_MULTIPLE) + generate_presorted_paths(root, rel); + /* Now find the cheapest of the paths for this rel */ set_cheapest(rel); @@ -3382,6 +3393,48 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel, return useful_pathkeys_list; } +/* + * generate_presorted_paths + * Add an explicitly sorted path on a useful prefix of query_pathkeys. + * + * query_pathkeys stands for whatever ordering the upper planner wants first: + * GROUP BY, window, DISTINCT or ORDER BY. A relation sorted on a prefix of + * it pays off wherever a consumer above can exploit an existing order. + * + * Index paths with such an ordering already survive in the pathlist. This + * provides the same for relations with no suitable index, or that are not plain + * tables at all. + */ +static void +generate_presorted_paths(PlannerInfo *root, RelOptInfo *rel) +{ + List *useful_pathkeys_list; + Path *cheapest; + + /* Nothing to order, or the rel is proven empty */ + if (root->query_pathkeys == NIL || IS_DUMMY_REL(rel)) + return; + + useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel, false); + if (useful_pathkeys_list == NIL) + return; + + /* rel->cheapest_total_path is not set yet; set_cheapest() comes later */ + cheapest = get_cheapest_path_for_pathkeys(rel->pathlist, NIL, NULL, + TOTAL_COST, false); + if (cheapest == NULL) + return; + + foreach_ptr(List, useful_pathkeys, useful_pathkeys_list) + { + if (pathkeys_contained_in(useful_pathkeys, cheapest->pathkeys)) + continue; + + add_path(rel, (Path *) create_sort_path(root, rel, cheapest, + useful_pathkeys, -1.0)); + } +} + /* * generate_useful_gather_paths * Generate parallel access paths for a relation by pushing a Gather or diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index 69b2f5109ad..b467d9aba60 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -3694,26 +3694,30 @@ INSERT INTO pagg_tab6 (b, c) SELECT substr('cdCD', (i % 4) + 1 , 1), substr('cdC ANALYZE pagg_tab6; EXPLAIN (COSTS OFF) SELECT t1.c, count(t2.c) FROM pagg_tab5 t1 JOIN pagg_tab6 t2 ON t1.c = t2.c AND t1.c = t2.b GROUP BY 1 ORDER BY t1.c COLLATE "C"; - QUERY PLAN -------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------- Sort Sort Key: t1.c COLLATE "C" -> Append - -> HashAggregate + -> GroupAggregate Group Key: t1.c -> Nested Loop Join Filter: (t1.c = t2.c) - -> Seq Scan on pagg_tab6_p1 t2 - Filter: (c = b) + -> Sort + Sort Key: t2.c COLLATE case_insensitive + -> Seq Scan on pagg_tab6_p1 t2 + Filter: (c = b) -> Seq Scan on pagg_tab5_p1 t1 - -> HashAggregate + -> GroupAggregate Group Key: t1_1.c -> Nested Loop Join Filter: (t1_1.c = t2_1.c) - -> Seq Scan on pagg_tab6_p2 t2_1 - Filter: (c = b) + -> Sort + Sort Key: t2_1.c COLLATE case_insensitive + -> Seq Scan on pagg_tab6_p2 t2_1 + Filter: (c = b) -> Seq Scan on pagg_tab5_p2 t1_1 -(17 rows) +(21 rows) SELECT t1.c, count(t2.c) FROM pagg_tab5 t1 JOIN pagg_tab6 t2 ON t1.c = t2.c AND t1.c = t2.b GROUP BY 1 ORDER BY t1.c COLLATE "C"; c | count diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index c1a95157251..f99b6ded9f9 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -201,9 +201,9 @@ SELECT pc.relkind, pa.amname, CASE WHEN relkind = 't' THEN - (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) + (SELECT 'toast for ' || pcm.oid::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) ELSE - relname::regclass::text + pc.oid::regclass::text END COLLATE "C" AS relname FROM pg_class AS pc, pg_am AS pa @@ -525,9 +525,9 @@ SELECT pc.relkind, pa.amname, CASE WHEN relkind = 't' THEN - (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) + (SELECT 'toast for ' || pcm.oid::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) ELSE - relname::regclass::text + pc.oid::regclass::text END COLLATE "C" AS relname FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out index bdda7da68f0..5d64e48a6f9 100644 --- a/src/test/regress/expected/eager_aggregate.out +++ b/src/test/regress/expected/eager_aggregate.out @@ -589,24 +589,24 @@ SELECT s1.a, sum(s2.c) FROM eager_agg_s1 s1 JOIN eager_agg_s2 s2 ON s1.a = s2.b GROUP BY s1.a; - QUERY PLAN ------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Finalize GroupAggregate Output: s1.a, sum(s2.c) Group Key: s1.a - -> Sort + -> Nested Loop Output: s1.a, (PARTIAL sum(s2.c)) - Sort Key: s1.a - -> Nested Loop - Output: s1.a, (PARTIAL sum(s2.c)) - Join Filter: (s1.a = s2.b) + Join Filter: (s1.a = s2.b) + -> Sort + Output: s1.a + Sort Key: s1.a -> Seq Scan on public.eager_agg_s1 s1 Output: s1.a - -> Partial HashAggregate - Output: s2.b, PARTIAL sum(s2.c) - Group Key: s2.b - -> Seq Scan on public.eager_agg_s2 s2 - Output: s2.b, s2.c + -> Partial HashAggregate + Output: s2.b, PARTIAL sum(s2.c) + Group Key: s2.b + -> Seq Scan on public.eager_agg_s2 s2 + Output: s2.b, s2.c (16 rows) SELECT s1.a, sum(s2.c) diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index 1e6e020fea8..5b9ab4494c5 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1610,16 +1610,16 @@ from tenk1 t, generate_series(1, 1000); QUERY PLAN --------------------------------------------------------------------------------- Unique - -> Sort - Sort Key: t.unique1, ((SubPlan expr_1)) - -> Gather - Workers Planned: 2 - -> Nested Loop + -> Nested Loop + -> Sort + Sort Key: t.unique1, ((SubPlan expr_1)) + -> Gather + Workers Planned: 2 -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t - -> Function Scan on generate_series - SubPlan expr_1 - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = t.unique1) + SubPlan expr_1 + -> Index Only Scan using tenk1_unique1 on tenk1 + Index Cond: (unique1 = t.unique1) + -> Function Scan on generate_series (11 rows) explain (costs off) select @@ -1629,16 +1629,16 @@ from tenk1 t, generate_series(1, 1000) order by 1, 2; QUERY PLAN --------------------------------------------------------------------------- - Sort - Sort Key: t.unique1, ((SubPlan expr_1)) - -> Gather - Workers Planned: 2 - -> Nested Loop + Nested Loop + -> Sort + Sort Key: t.unique1, ((SubPlan expr_1)) + -> Gather + Workers Planned: 2 -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t - -> Function Scan on generate_series - SubPlan expr_1 - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = t.unique1) + SubPlan expr_1 + -> Index Only Scan using tenk1_unique1 on tenk1 + Index Cond: (unique1 = t.unique1) + -> Function Scan on generate_series (10 rows) -- Parallel sort but with expression not available until the upper rel. @@ -1703,15 +1703,17 @@ explain (costs off) select a, b, a <-> point(5, 5) dist from point_table order b Order By: (a <-> '(5,5)'::point) (6 rows) --- Ensure we get an incremental sort on the outer side of the mergejoin +-- Ensure we get an incremental sort on the outer side of the mergejoin. +-- Use a left join so that the ORDER BY can only be satisfied by t1. +set enable_nestloop = off; explain (costs off) select * from - (select * from tenk1 order by four) t1 join tenk1 t2 on t1.four = t2.four and t1.two = t2.two + (select * from tenk1 order by four) t1 left join tenk1 t2 on t1.four = t2.four and t1.two = t2.two order by t1.four, t1.two limit 1; QUERY PLAN ----------------------------------------------------------------------- Limit - -> Merge Join + -> Merge Left Join Merge Cond: ((tenk1.four = t2.four) AND (tenk1.two = t2.two)) -> Incremental Sort Sort Key: tenk1.four, tenk1.two @@ -1724,6 +1726,7 @@ order by t1.four, t1.two limit 1; -> Seq Scan on tenk1 t2 (12 rows) +reset enable_nestloop; -- -- Test incremental sort for Append/MergeAppend -- diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 036fd39174f..4ae13414617 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -2050,16 +2050,20 @@ order by t1.b limit 10; Merge Cond: (t1.b = t2.b) -> Merge Append Sort Key: t1.b - -> Index Scan using matest0i on matest0 t1_1 + -> Sort + Sort Key: t1_1.b + -> Seq Scan on matest0 t1_1 -> Index Scan using matest1i on matest1 t1_2 -> Materialize -> Merge Append Sort Key: t2.b - -> Index Scan using matest0i on matest0 t2_1 - Filter: (c = d) + -> Sort + Sort Key: t2_1.b + -> Seq Scan on matest0 t2_1 + Filter: (c = d) -> Index Scan using matest1i on matest1 t2_2 Filter: (c = d) -(14 rows) +(18 rows) reset enable_nestloop; drop table matest0 cascade; diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 4ec9291833b..9d59c337d3d 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2844,18 +2844,16 @@ order by x.thousand desc, x.twothousand; ---------------------------------------------------------------------------------------- Finalize GroupAggregate Group Key: x.thousand, x.twothousand - -> Incremental Sort - Sort Key: x.thousand DESC, x.twothousand - Presorted Key: x.thousand - -> Merge Join - Merge Cond: (y.thousand = x.thousand) + -> Merge Join + Merge Cond: (x.thousand = y.thousand) + -> Sort + Sort Key: x.thousand DESC, x.twothousand + -> Seq Scan on tenk1 x + -> Materialize -> Partial GroupAggregate Group Key: y.thousand -> Index Only Scan Backward using tenk1_thous_tenthous on tenk1 y - -> Sort - Sort Key: x.thousand DESC - -> Seq Scan on tenk1 x -(13 rows) +(11 rows) reset enable_hashagg; reset enable_nestloop; @@ -4524,29 +4522,29 @@ from int8_tbl t1 left join cross join (select from int4_tbl where f1 = 0) t3) t23 on (t1.q1 = 123) order by 1, 2; - QUERY PLAN ----------------------------------------------------------------- - Sort - Output: t1.q1, t1.q2, ((SubPlan expr_1)) - Sort Key: t1.q1, t1.q2 - -> Nested Loop Left Join - Output: t1.q1, t1.q2, (SubPlan expr_1) - Join Filter: (t1.q1 = 123) + QUERY PLAN +---------------------------------------------------------- + Nested Loop Left Join + Output: t1.q1, t1.q2, (SubPlan expr_1) + Join Filter: (t1.q1 = 123) + -> Sort + Output: t1.q1, t1.q2 + Sort Key: t1.q1, t1.q2 -> Seq Scan on public.int8_tbl t1 Output: t1.q1, t1.q2 - -> Materialize - Output: ('()'::record) - -> Nested Loop - Output: '()'::record - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - Filter: (int4_tbl.f1 = 0) - -> Seq Scan on public.int4_tbl int4_tbl_1 - Output: int4_tbl_1.f1 - Filter: (int4_tbl_1.f1 = 0) - SubPlan expr_1 - -> Result - Output: (('()'::record))::text + -> Materialize + Output: ('()'::record) + -> Nested Loop + Output: '()'::record + -> Seq Scan on public.int4_tbl + Output: int4_tbl.f1 + Filter: (int4_tbl.f1 = 0) + -> Seq Scan on public.int4_tbl int4_tbl_1 + Output: int4_tbl_1.f1 + Filter: (int4_tbl_1.f1 = 0) + SubPlan expr_1 + -> Result + Output: (('()'::record))::text (21 rows) select t1.q1, t1.q2, (select t23::text) @@ -4573,31 +4571,31 @@ from int8_tbl t1 left join on (t1.q1 = 123), length(t23::text) f order by 1, 2; - QUERY PLAN ----------------------------------------------------------------------- - Sort + QUERY PLAN +---------------------------------------------------------------- + Nested Loop Output: t1.q1, t1.q2, f.f - Sort Key: t1.q1, t1.q2 - -> Nested Loop - Output: t1.q1, t1.q2, f.f - -> Nested Loop Left Join - Output: t1.q1, t1.q2, ('()'::record) - Join Filter: (t1.q1 = 123) + -> Nested Loop Left Join + Output: t1.q1, t1.q2, ('()'::record) + Join Filter: (t1.q1 = 123) + -> Sort + Output: t1.q1, t1.q2 + Sort Key: t1.q1, t1.q2 -> Seq Scan on public.int8_tbl t1 Output: t1.q1, t1.q2 - -> Materialize - Output: ('()'::record) - -> Nested Loop Left Join - Output: '()'::record - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - Filter: (int4_tbl.f1 = 0) - -> Seq Scan on public.int4_tbl int4_tbl_1 - Output: int4_tbl_1.f1 - Filter: (int4_tbl_1.f1 = 0) - -> Function Scan on pg_catalog.length f - Output: f.f - Function Call: length((('()'::record))::text) + -> Materialize + Output: ('()'::record) + -> Nested Loop Left Join + Output: '()'::record + -> Seq Scan on public.int4_tbl + Output: int4_tbl.f1 + Filter: (int4_tbl.f1 = 0) + -> Seq Scan on public.int4_tbl int4_tbl_1 + Output: int4_tbl_1.f1 + Filter: (int4_tbl_1.f1 = 0) + -> Function Scan on pg_catalog.length f + Output: f.f + Function Call: length((('()'::record))::text) (23 rows) select t1.q1, t1.q2, f @@ -4741,21 +4739,21 @@ from int4_tbl i4 left join (lateral (select i4.f1) ss(x) cross join (select 1) s2(y)) j on (i4.f1 = 0) order by 1; - QUERY PLAN -------------------------------------------------- - Sort - Output: i4.f1, ((SubPlan expr_1)) - Sort Key: i4.f1 - -> Nested Loop Left Join - Output: i4.f1, (SubPlan expr_1) - Join Filter: (i4.f1 = 0) + QUERY PLAN +-------------------------------------------- + Nested Loop Left Join + Output: i4.f1, (SubPlan expr_1) + Join Filter: (i4.f1 = 0) + -> Sort + Output: i4.f1 + Sort Key: i4.f1 -> Seq Scan on public.int4_tbl i4 Output: i4.f1 - -> Result - Output: ROW(i4.f1, 1) - SubPlan expr_1 - -> Result - Output: ((ROW(i4.f1, 1)))::text + -> Result + Output: ROW(i4.f1, 1) + SubPlan expr_1 + -> Result + Output: ((ROW(i4.f1, 1)))::text (13 rows) select i4.f1, (select j::text) @@ -8526,19 +8524,18 @@ select d.* from d left join (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s on d.a = s.id order by 1, 2; - QUERY PLAN ------------------------------------------------------------------------ - Sort - Sort Key: d.a, d.b - -> Hash Left Join - Hash Cond: (d.a = s.id) + QUERY PLAN +----------------------------------------------------------- + Merge Left Join + Merge Cond: (d.a = b.id) + -> Sort + Sort Key: d.a, d.b -> Seq Scan on d - -> Hash - -> Subquery Scan on s - -> ProjectSet - -> Unique - -> Index Only Scan using b_pkey on b -(10 rows) + -> Materialize + -> ProjectSet + -> Unique + -> Index Only Scan using b_pkey on b +(9 rows) select d.* from d left join (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s @@ -11704,6 +11701,55 @@ select * from 0 | 9998 | 0 (1 row) +-- Relation with only parameterized paths, gets no presorted path. +explain (costs off) +select x.u +from int8_tbl a, int8_tbl b, lateral unnest(array[a.q1, a.q2]) as x(u) +where x.u = b.q1 +order by x.u; + QUERY PLAN +--------------------------------------------- + Merge Join + Merge Cond: (b.q1 = x.u) + -> Sort + Sort Key: b.q1 + -> Seq Scan on int8_tbl b + -> Sort + Sort Key: x.u + -> Nested Loop + -> Seq Scan on int8_tbl a + -> Function Scan on unnest x +(10 rows) + +select x.u +from int8_tbl a, int8_tbl b, lateral unnest(array[a.q1, a.q2]) as x(u) +where x.u = b.q1 +order by x.u; + u +------------------ + 123 + 123 + 123 + 123 + 123 + 123 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 + 4567890123456789 +(21 rows) + -- check proper extParam/allParam handling (this isn't exactly a LATERAL issue, -- but we can make the test case much more compact with LATERAL) explain (verbose, costs off) @@ -12049,29 +12095,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort + QUERY PLAN +-------------------------------------------------------------- + Nested Loop Left Join Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a - Sort Key: t1.a - -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + -> Sort + Output: t1.b, t1.a + Sort Key: t1.a -> Seq Scan on public.join_ut1 t1 - Output: t1.a, t1.b, t1.c - -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) - -> Seq Scan on public.join_ut1 t3 - Output: t3.a, t3.b, t3.c - -> Hash - Output: t2.a - -> Append - -> Seq Scan on public.join_pt1p1p1 t2_1 - Output: t2_1.a - Filter: (t1.a = t2_1.a) - -> Seq Scan on public.join_pt1p2 t2_2 - Output: t2_2.a - Filter: (t1.a = t2_2.a) + Output: t1.b, t1.a + -> Hash Join + Output: t2.a, LEAST(t1.a, t2.a, t3.a) + Hash Cond: (t3.b = t2.a) + -> Seq Scan on public.join_ut1 t3 + Output: t3.a, t3.b, t3.c + -> Hash + Output: t2.a + -> Append + -> Seq Scan on public.join_pt1p1p1 t2_1 + Output: t2_1.a + Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2_2 + Output: t2_2.a + Filter: (t1.a = t2_2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral @@ -12816,3 +12862,56 @@ WHERE s.x = s.c; (1 row) ROLLBACK; +-- +-- Test sort-ahead: a relation without an index on the ORDER BY key is +-- sorted below the join, instead of sorting the join result +-- +BEGIN; +CREATE TEMP TABLE sa_outer AS + SELECT g AS id, (g * 7919) % 1000 AS v FROM generate_series(1, 1000) g; +CREATE TEMP TABLE sa_inner AS + SELECT g % 1000 + 1 AS oid, g AS x FROM generate_series(1, 20000) g; +CREATE INDEX ON sa_inner (oid); +ANALYZE sa_outer, sa_inner; +-- The outer side is sorted on the ORDER BY prefix that it can provide, and +-- an Incremental Sort above the join completes the order +EXPLAIN (COSTS OFF) +SELECT o.v, i.x FROM sa_outer o JOIN sa_inner i ON i.oid = o.id +ORDER BY o.v, i.x LIMIT 5; + QUERY PLAN +------------------------------------------------------------------- + Limit + -> Incremental Sort + Sort Key: o.v, i.x + Presorted Key: o.v + -> Nested Loop + -> Sort + Sort Key: o.v + -> Seq Scan on sa_outer o + -> Index Scan using sa_inner_oid_idx on sa_inner i + Index Cond: (oid = o.id) +(10 rows) + +SELECT o.v, i.x FROM sa_outer o JOIN sa_inner i ON i.oid = o.id +ORDER BY o.v, i.x LIMIT 5; + v | x +---+------ + 0 | 999 + 0 | 1999 + 0 | 2999 + 0 | 3999 + 0 | 4999 +(5 rows) + +-- With a single relation there is no join to sort below +EXPLAIN (COSTS OFF) +SELECT * FROM sa_outer ORDER BY v LIMIT 5; + QUERY PLAN +---------------------------------- + Limit + -> Sort + Sort Key: v + -> Seq Scan on sa_outer +(4 rows) + +ROLLBACK; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index e53c0c1a4d8..68cdd46b12f 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -445,36 +445,41 @@ EXPLAIN (COSTS OFF) SELECT * FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------------------- - Sort + QUERY PLAN +-------------------------------------------------------------------- + Merge Append Sort Key: t1.a - -> Append - -> Nested Loop Left Join + -> Nested Loop Left Join + -> Sort + Sort Key: t1_1.a -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 - Index Cond: (a = t1_1.a) - -> Index Scan using iprt2_p1_b on prt2_p1 t3_1 - Index Cond: (b = t2_1.a) - -> Nested Loop Left Join + -> Nested Loop + -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 + Index Cond: (a = t1_1.a) + -> Index Scan using iprt2_p1_b on prt2_p1 t3_1 + Index Cond: (b = t2_1.a) + -> Nested Loop Left Join + -> Sort + Sort Key: t1_2.a -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 - Index Cond: (a = t1_2.a) - -> Index Scan using iprt2_p2_b on prt2_p2 t3_2 - Index Cond: (b = t2_2.a) - -> Nested Loop Left Join + -> Nested Loop + -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 + Index Cond: (a = t1_2.a) + -> Index Scan using iprt2_p2_b on prt2_p2 t3_2 + Index Cond: (b = t2_2.a) + -> Nested Loop Left Join + -> Sort + Sort Key: t1_3.a -> Seq Scan on prt1_p3 t1_3 Filter: (b = 0) - -> Nested Loop - -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 - Index Cond: (a = t1_3.a) - -> Index Scan using iprt2_p3_b on prt2_p3 t3_3 - Index Cond: (b = t2_3.a) -(27 rows) + -> Nested Loop + -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 + Index Cond: (a = t1_3.a) + -> Index Scan using iprt2_p3_b on prt2_p3 t3_3 + Index Cond: (b = t2_3.a) +(32 rows) SELECT * FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss @@ -724,30 +729,35 @@ EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN (SELECT b, COALESCE(c, 'x') AS c FROM prt2 WHERE a = 0) t2 ON t1.a = t2.b WHERE t1.c = t2.c ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------------------- - Sort + QUERY PLAN +------------------------------------------------------------------------------------------ + Merge Append Sort Key: t1.a - -> Append - -> Nested Loop + -> Nested Loop + -> Sort + Sort Key: prt2_1.b -> Seq Scan on prt2_p1 prt2_1 Filter: (a = 0) - -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 - Index Cond: (a = prt2_1.b) - Filter: (((COALESCE(prt2_1.c, 'x'::character varying)))::text = (c)::text) - -> Nested Loop + -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 + Index Cond: (a = prt2_1.b) + Filter: (((COALESCE(prt2_1.c, 'x'::character varying)))::text = (c)::text) + -> Nested Loop + -> Sort + Sort Key: prt2_2.b -> Seq Scan on prt2_p2 prt2_2 Filter: (a = 0) - -> Index Scan using iprt1_p2_a on prt1_p2 t1_2 - Index Cond: (a = prt2_2.b) - Filter: (((COALESCE(prt2_2.c, 'x'::character varying)))::text = (c)::text) - -> Nested Loop + -> Index Scan using iprt1_p2_a on prt1_p2 t1_2 + Index Cond: (a = prt2_2.b) + Filter: (((COALESCE(prt2_2.c, 'x'::character varying)))::text = (c)::text) + -> Nested Loop + -> Sort + Sort Key: prt2_3.b -> Seq Scan on prt2_p3 prt2_3 Filter: (a = 0) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 - Index Cond: (a = prt2_3.b) - Filter: (((COALESCE(prt2_3.c, 'x'::character varying)))::text = (c)::text) -(21 rows) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = prt2_3.b) + Filter: (((COALESCE(prt2_3.c, 'x'::character varying)))::text = (c)::text) +(26 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN (SELECT b, COALESCE(c, 'x') AS c FROM prt2 WHERE a = 0) t2 ON t1.a = t2.b @@ -783,34 +793,42 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM prt4 t1 LEFT JOIN WHERE t1.c = t2.c AND t2.a IS NOT NULL ORDER BY t1.a, t1.c; QUERY PLAN --------------------------------------------------------------------------------------------------- - Sort + Incremental Sort Sort Key: t1.a, t1.c - -> Append + Presorted Key: t1.a + -> Merge Append + Sort Key: t1.a -> Nested Loop -> Nested Loop - -> Seq Scan on prt1_p1 t4_1 - Filter: (b = 0) + -> Sort + Sort Key: t4_1.a + -> Seq Scan on prt1_p1 t4_1 + Filter: (b = 0) -> Index Scan using prt3_p1_a_idx on prt3_p1 t3_1 Index Cond: ((a = t4_1.a) AND (a IS NOT NULL)) -> Index Only Scan using prt4_p1_c_a_idx on prt4_p1 t1_1 Index Cond: ((c = ((COALESCE(t3_1.c, t4_1.c)))::text) AND (a = t3_1.a)) -> Nested Loop -> Nested Loop - -> Seq Scan on prt1_p2 t4_2 - Filter: (b = 0) + -> Sort + Sort Key: t4_2.a + -> Seq Scan on prt1_p2 t4_2 + Filter: (b = 0) -> Index Scan using prt3_p2_a_idx on prt3_p2 t3_2 Index Cond: ((a = t4_2.a) AND (a IS NOT NULL)) -> Index Only Scan using prt4_p2_c_a_idx on prt4_p2 t1_2 Index Cond: ((c = ((COALESCE(t3_2.c, t4_2.c)))::text) AND (a = t3_2.a)) -> Nested Loop - -> Seq Scan on prt1_p3 t4_3 - Filter: (b = 0) + -> Sort + Sort Key: t4_3.a + -> Seq Scan on prt1_p3 t4_3 + Filter: (b = 0) -> Nested Loop -> Index Scan using prt3_p3_a_idx on prt3_p3 t3_3 Index Cond: ((a = t4_3.a) AND (a IS NOT NULL)) -> Index Only Scan using prt4_p3_c_a_idx on prt4_p3 t1_3 Index Cond: ((c = ((COALESCE(t3_3.c, t4_3.c)))::text) AND (a = t3_3.a)) -(27 rows) +(35 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM prt4 t1 LEFT JOIN (SELECT t3.a, COALESCE(t3.c, t4.c) AS c FROM prt3 t3 JOIN prt1 t4 ON t3.a = t4.a @@ -1410,8 +1428,8 @@ SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * F -- Semi-join EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------- Merge Append Sort Key: t1.a -> Nested Loop @@ -1445,13 +1463,13 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER -> Nested Loop Join Filter: (t1_4.a = t1_7.b) -> Unique - -> Sort - Sort Key: t1_7.b - -> Nested Loop + -> Nested Loop + -> Sort + Sort Key: t1_7.b -> Seq Scan on prt2_p3 t1_7 Filter: (a = 0) - -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_3 - Index Cond: (((a + b) / 2) = t1_7.b) + -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_3 + Index Cond: (((a + b) / 2) = t1_7.b) -> Index Scan using iprt1_p3_a on prt1_p3 t1_4 Index Cond: (a = ((t2_3.a + t2_3.b) / 2)) Filter: (b = 0) @@ -1670,11 +1688,13 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * Presorted Key: prt1.a -> Merge Left Join Merge Cond: (prt1.a = prt2.b) - -> Sort - Sort Key: prt1.a - -> Append + -> Append + -> Sort + Sort Key: prt1_1.a -> Seq Scan on prt1_p1 prt1_1 Filter: ((a < 450) AND (b = 0)) + -> Sort + Sort Key: prt1_2.a -> Seq Scan on prt1_p2 prt1_2 Filter: ((a < 450) AND (b = 0)) -> Sort @@ -1684,7 +1704,7 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * Filter: (b > 250) -> Seq Scan on prt2_p3 prt2_2 Filter: (b > 250) -(19 rows) +(21 rows) SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | b @@ -1704,25 +1724,33 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * -- partitionwise join does not apply EXPLAIN (COSTS OFF) SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------ Merge Join - Merge Cond: ((t1.a = t2.b) AND (((((t1.*)::prt1))::text) = ((((t2.*)::prt2))::text))) - -> Sort - Sort Key: t1.a, ((((t1.*)::prt1))::text) - -> Result - -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 - -> Sort - Sort Key: t2.b, ((((t2.*)::prt2))::text) - -> Result - -> Append + Merge Cond: (t1.a = t2.b) + Join Filter: ((((t2.*)::prt2))::text = (((t1.*)::prt1))::text) + -> Append + -> Sort + Sort Key: t1_1.a + -> Seq Scan on prt1_p1 t1_1 + -> Sort + Sort Key: t1_2.a + -> Seq Scan on prt1_p2 t1_2 + -> Sort + Sort Key: t1_3.a + -> Seq Scan on prt1_p3 t1_3 + -> Materialize + -> Append + -> Sort + Sort Key: t2_1.b -> Seq Scan on prt2_p1 t2_1 + -> Sort + Sort Key: t2_2.b -> Seq Scan on prt2_p2 t2_2 + -> Sort + Sort Key: t2_3.b -> Seq Scan on prt2_p3 t2_3 -(16 rows) +(24 rows) SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; a | b @@ -2376,53 +2404,60 @@ EXPLAIN (COSTS OFF) SELECT * FROM prt1_l t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; - QUERY PLAN ------------------------------------------------------------------------------------------------ - Sort + QUERY PLAN +----------------------------------------------------------------------------------------- + Merge Append Sort Key: t1.a - -> Append - -> Nested Loop Left Join + -> Nested Loop Left Join + -> Sort + Sort Key: t1_1.a -> Seq Scan on prt1_l_p1 t1_1 Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_1.b = t2_1.a) AND ((t3_1.c)::text = (t2_1.c)::text)) - -> Seq Scan on prt2_l_p1 t3_1 - -> Hash - -> Seq Scan on prt1_l_p1 t2_1 - Filter: ((t1_1.a = a) AND ((t1_1.c)::text = (c)::text)) - -> Nested Loop Left Join + -> Hash Join + Hash Cond: ((t3_1.b = t2_1.a) AND ((t3_1.c)::text = (t2_1.c)::text)) + -> Seq Scan on prt2_l_p1 t3_1 + -> Hash + -> Seq Scan on prt1_l_p1 t2_1 + Filter: ((t1_1.a = a) AND ((t1_1.c)::text = (c)::text)) + -> Nested Loop Left Join + -> Sort + Sort Key: t1_2.a -> Seq Scan on prt1_l_p2_p1 t1_2 Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_2.b = t2_2.a) AND ((t3_2.c)::text = (t2_2.c)::text)) - -> Seq Scan on prt2_l_p2_p1 t3_2 - -> Hash - -> Seq Scan on prt1_l_p2_p1 t2_2 - Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text)) - -> Nested Loop Left Join + -> Hash Join + Hash Cond: ((t3_2.b = t2_2.a) AND ((t3_2.c)::text = (t2_2.c)::text)) + -> Seq Scan on prt2_l_p2_p1 t3_2 + -> Hash + -> Seq Scan on prt1_l_p2_p1 t2_2 + Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text)) + -> Nested Loop Left Join + -> Sort + Sort Key: t1_3.a -> Seq Scan on prt1_l_p2_p2 t1_3 Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text)) - -> Seq Scan on prt2_l_p2_p2 t3_3 - -> Hash - -> Seq Scan on prt1_l_p2_p2 t2_3 - Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) - -> Nested Loop Left Join + -> Hash Join + Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text)) + -> Seq Scan on prt2_l_p2_p2 t3_3 + -> Hash + -> Seq Scan on prt1_l_p2_p2 t2_3 + Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) + -> Nested Loop Left Join + -> Sort + Sort Key: t1_4.a -> Seq Scan on prt1_l_p3_p1 t1_4 Filter: (b = 0) - -> Hash Join - Hash Cond: ((t3_5.b = t2_5.a) AND ((t3_5.c)::text = (t2_5.c)::text)) + -> Hash Join + Hash Cond: ((t3_5.b = t2_5.a) AND ((t3_5.c)::text = (t2_5.c)::text)) + -> Append + -> Seq Scan on prt2_l_p3_p1 t3_5 + -> Seq Scan on prt2_l_p3_p2 t3_6 + -> Hash -> Append - -> Seq Scan on prt2_l_p3_p1 t3_5 - -> Seq Scan on prt2_l_p3_p2 t3_6 - -> Hash - -> Append - -> Seq Scan on prt1_l_p3_p1 t2_5 - Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) - -> Seq Scan on prt1_l_p3_p2 t2_6 - Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) -(44 rows) + -> Seq Scan on prt1_l_p3_p1 t2_5 + Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) + -> Seq Scan on prt1_l_p3_p2 t2_6 + Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text)) +(51 rows) SELECT * FROM prt1_l t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss @@ -5417,36 +5452,48 @@ EXPLAIN (COSTS OFF) SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------- - Sort + Merge Append Sort Key: t1.a, t1.b - -> Append - -> Hash Join - Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) + -> Merge Join + Merge Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b)) + Join Filter: (t1_1.c = t2_1.c) + -> Sort + Sort Key: t1_1.a, t1_1.b -> Seq Scan on alpha_neg_p1 t1_1 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Hash - -> Seq Scan on beta_neg_p1 t2_1 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Hash Join - Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) + -> Sort + Sort Key: t2_1.a, t2_1.b + -> Seq Scan on beta_neg_p1 t2_1 + Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) + -> Merge Join + Merge Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b)) + Join Filter: (t1_2.c = t2_2.c) + -> Sort + Sort Key: t1_2.a, t1_2.b -> Seq Scan on alpha_neg_p2 t1_2 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - -> Hash - -> Seq Scan on beta_neg_p2 t2_2 - Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) - -> Seq Scan on alpha_pos_p2 t1_3 - Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) + -> Sort + Sort Key: t2_2.a, t2_2.b + -> Seq Scan on beta_neg_p2 t2_2 + Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) + -> Nested Loop + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) + -> Sort + Sort Key: t2_3.a, t2_3.b -> Seq Scan on beta_pos_p2 t2_3 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) - -> Nested Loop - Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c)) - -> Seq Scan on alpha_pos_p3 t1_4 + -> Materialize + -> Seq Scan on alpha_pos_p2 t1_3 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) + -> Nested Loop + Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c)) + -> Sort + Sort Key: t2_4.a, t2_4.b -> Seq Scan on beta_pos_p3 t2_4 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -(29 rows) + -> Seq Scan on alpha_pos_p3 t1_4 + Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) +(41 rows) SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; a | b | c | a | b | c diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 4fbfd5ca1ac..d603a633b69 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -911,9 +911,12 @@ order by t1.a, t2.a; Output: t2_1.a, t2_1.b -> Index Scan using unique_tbl_p2_a_idx on public.unique_tbl_p2 t2_2 Output: t2_2.a, t2_2.b - -> Index Scan using unique_tbl_p3_a_idx on public.unique_tbl_p3 t2_3 + -> Sort Output: t2_3.a, t2_3.b -(44 rows) + Sort Key: t2_3.a + -> Seq Scan on public.unique_tbl_p3 t2_3 + Output: t2_3.a, t2_3.b +(47 rows) reset enable_partitionwise_join; drop table unique_tbl_p; diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out index 589d5c3054c..5a643119d51 100644 --- a/src/test/regress/expected/tablesample.out +++ b/src/test/regress/expected/tablesample.out @@ -253,13 +253,15 @@ select pct, count(unique1) from group by pct; QUERY PLAN -------------------------------------------------------- - HashAggregate + GroupAggregate Group Key: "*VALUES*".column1 -> Nested Loop - -> Values Scan on "*VALUES*" + -> Sort + Sort Key: "*VALUES*".column1 + -> Values Scan on "*VALUES*" -> Sample Scan on tenk1 Sampling: bernoulli ("*VALUES*".column1) -(6 rows) +(8 rows) select pct, count(unique1) from (values (0),(100)) v(pct), diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out index fc1321bf443..66bc1ea6200 100644 --- a/src/test/regress/expected/tuplesort.out +++ b/src/test/regress/expected/tuplesort.out @@ -635,18 +635,15 @@ EXPLAIN (COSTS OFF) :qry; -> GroupAggregate Group Key: a.col12 Filter: (count(*) > 1) - -> Incremental Sort - Sort Key: a.col12 DESC, a.col1 - Presorted Key: a.col12 - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(17 rows) + -> Merge Join + Merge Cond: (a.col12 = b.col12) + -> Sort + Sort Key: a.col12 DESC, a.col1 + -> Seq Scan on test_mark_restore a + -> Sort + Sort Key: b.col12 DESC + -> Seq Scan on test_mark_restore b +(14 rows) :qry; col12 | count | count | count | count | count @@ -674,18 +671,15 @@ EXPLAIN (COSTS OFF) :qry; -> GroupAggregate Group Key: a.col12 Filter: (count(*) > 1) - -> Incremental Sort - Sort Key: a.col12 DESC, a.col1 - Presorted Key: a.col12 - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(17 rows) + -> Merge Join + Merge Cond: (a.col12 = b.col12) + -> Sort + Sort Key: a.col12 DESC, a.col1 + -> Seq Scan on test_mark_restore a + -> Sort + Sort Key: b.col12 DESC + -> Seq Scan on test_mark_restore b +(14 rows) :qry; col12 | count | count | count | count | count diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 754fe0c694b..b5c970fcec2 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -146,9 +146,9 @@ SELECT pc.relkind, pa.amname, CASE WHEN relkind = 't' THEN - (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) + (SELECT 'toast for ' || pcm.oid::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) ELSE - relname::regclass::text + pc.oid::regclass::text END COLLATE "C" AS relname FROM pg_class AS pc, pg_am AS pa @@ -339,9 +339,9 @@ SELECT pc.relkind, pa.amname, CASE WHEN relkind = 't' THEN - (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) + (SELECT 'toast for ' || pcm.oid::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid) ELSE - relname::regclass::text + pc.oid::regclass::text END COLLATE "C" AS relname FROM pg_class AS pc LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam) diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql index bbe658a7588..3694e0f73f0 100644 --- a/src/test/regress/sql/incremental_sort.sql +++ b/src/test/regress/sql/incremental_sort.sql @@ -293,11 +293,14 @@ create index point_table_a_idx on point_table using gist(a); explain (costs off) select a, b, a <-> point(5, 5) dist from point_table order by dist, b limit 1; explain (costs off) select a, b, a <-> point(5, 5) dist from point_table order by dist, b desc limit 1; --- Ensure we get an incremental sort on the outer side of the mergejoin +-- Ensure we get an incremental sort on the outer side of the mergejoin. +-- Use a left join so that the ORDER BY can only be satisfied by t1. +set enable_nestloop = off; explain (costs off) select * from - (select * from tenk1 order by four) t1 join tenk1 t2 on t1.four = t2.four and t1.two = t2.two + (select * from tenk1 order by four) t1 left join tenk1 t2 on t1.four = t2.four and t1.two = t2.two order by t1.four, t1.two limit 1; +reset enable_nestloop; -- -- Test incremental sort for Append/MergeAppend diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 381c57a5a88..341bec76c0f 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -4418,6 +4418,17 @@ select * from where f1 = any (select unique1 from tenk1 where unique2 = v.x offset 0)) ss; +-- Relation with only parameterized paths, gets no presorted path. +explain (costs off) +select x.u +from int8_tbl a, int8_tbl b, lateral unnest(array[a.q1, a.q2]) as x(u) +where x.u = b.q1 +order by x.u; +select x.u +from int8_tbl a, int8_tbl b, lateral unnest(array[a.q1, a.q2]) as x(u) +where x.u = b.q1 +order by x.u; + -- check proper extParam/allParam handling (this isn't exactly a LATERAL issue, -- but we can make the test case much more compact with LATERAL) explain (verbose, costs off) @@ -4919,3 +4930,30 @@ FROM (VALUES (1)) AS v(z) LEFT JOIN WHERE s.x = s.c; ROLLBACK; + +-- +-- Test sort-ahead: a relation without an index on the ORDER BY key is +-- sorted below the join, instead of sorting the join result +-- +BEGIN; + +CREATE TEMP TABLE sa_outer AS + SELECT g AS id, (g * 7919) % 1000 AS v FROM generate_series(1, 1000) g; +CREATE TEMP TABLE sa_inner AS + SELECT g % 1000 + 1 AS oid, g AS x FROM generate_series(1, 20000) g; +CREATE INDEX ON sa_inner (oid); +ANALYZE sa_outer, sa_inner; + +-- The outer side is sorted on the ORDER BY prefix that it can provide, and +-- an Incremental Sort above the join completes the order +EXPLAIN (COSTS OFF) +SELECT o.v, i.x FROM sa_outer o JOIN sa_inner i ON i.oid = o.id +ORDER BY o.v, i.x LIMIT 5; +SELECT o.v, i.x FROM sa_outer o JOIN sa_inner i ON i.oid = o.id +ORDER BY o.v, i.x LIMIT 5; + +-- With a single relation there is no join to sort below +EXPLAIN (COSTS OFF) +SELECT * FROM sa_outer ORDER BY v LIMIT 5; + +ROLLBACK; -- 2.56.0