diff options
| author | Michael P | 2011-07-05 03:16:11 +0000 |
|---|---|---|
| committer | Michael P | 2011-07-06 03:40:35 +0000 |
| commit | 0bbfc1e6338b5d98d6cb83fa75f2c38f527d4d4b (patch) | |
| tree | 46fa412a31d08ea6e53d488ae7bc231df0b273da /src/backend/optimizer | |
| parent | 091b0e828cf0fd5bbd1f9ae58ab96fc983e55d77 (diff) | |
| parent | a4bebdd92624e018108c2610fc3f2c1584b6c687 (diff) | |
Merge commit 'a4bebdd92624e018108c2610fc3f2c1584b6c687' into master
This is the commit merge of Postgres-XC with the intersection of
PostgreSQL REL9_1_STABLE and master branches.
Conflicts:
COPYRIGHT
contrib/pgbench/pgbench.c
src/Makefile
src/backend/access/transam/recovery.conf.sample
src/backend/access/transam/varsup.c
src/backend/access/transam/xlog.c
src/backend/catalog/Makefile
src/backend/catalog/dependency.c
src/backend/catalog/system_views.sql
src/backend/commands/copy.c
src/backend/commands/explain.c
src/backend/commands/sequence.c
src/backend/commands/tablecmds.c
src/backend/commands/vacuum.c
src/backend/executor/nodeAgg.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/setrefs.c
src/backend/parser/gram.y
src/backend/parser/parse_utilcmd.c
src/backend/postmaster/postmaster.c
src/backend/rewrite/rewriteHandler.c
src/backend/storage/lmgr/proc.c
src/backend/tcop/postgres.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/backend/utils/sort/tuplesort.c
src/bin/initdb/initdb.c
src/bin/pg_ctl/pg_ctl.c
src/bin/pg_dump/pg_dump.c
src/include/access/xlog.h
src/include/catalog/catversion.h
src/include/catalog/indexing.h
src/include/catalog/pg_aggregate.h
src/include/catalog/pg_proc.h
src/include/commands/copy.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/optimizer/pathnode.h
src/include/parser/kwlist.h
src/include/storage/procarray.h
src/test/regress/expected/.gitignore
src/test/regress/expected/aggregates.out
src/test/regress/expected/alter_table.out
src/test/regress/expected/bit.out
src/test/regress/expected/box.out
src/test/regress/expected/delete.out
src/test/regress/expected/float4.out
src/test/regress/expected/float8.out
src/test/regress/expected/int2.out
src/test/regress/expected/int8.out
src/test/regress/expected/interval.out
src/test/regress/expected/numeric.out
src/test/regress/expected/point.out
src/test/regress/expected/polygon.out
src/test/regress/expected/sequence.out
src/test/regress/expected/timestamp.out
src/test/regress/expected/timestamptz.out
src/test/regress/expected/transactions.out
src/test/regress/expected/window.out
src/test/regress/input/misc.source
src/test/regress/output/create_misc_1.source
src/test/regress/output/misc.source
src/test/regress/sql/aggregates.sql
src/test/regress/sql/alter_table.sql
src/test/regress/sql/bit.sql
src/test/regress/sql/box.sql
src/test/regress/sql/delete.sql
src/test/regress/sql/domain.sql
src/test/regress/sql/float4.sql
src/test/regress/sql/float8.sql
src/test/regress/sql/int2.sql
src/test/regress/sql/int8.sql
src/test/regress/sql/interval.sql
src/test/regress/sql/lseg.sql
src/test/regress/sql/numeric.sql
src/test/regress/sql/path.sql
src/test/regress/sql/point.sql
src/test/regress/sql/polygon.sql
src/test/regress/sql/portals.sql
src/test/regress/sql/sequence.sql
src/test/regress/sql/timestamp.sql
src/test/regress/sql/timestamptz.sql
src/test/regress/sql/transactions.sql
src/test/regress/sql/window.sql
src/test/regress/sql/with.sql
Diffstat (limited to 'src/backend/optimizer')
55 files changed, 4883 insertions, 2242 deletions
diff --git a/src/backend/optimizer/Makefile b/src/backend/optimizer/Makefile index d134f90735..f523e5e33e 100644 --- a/src/backend/optimizer/Makefile +++ b/src/backend/optimizer/Makefile @@ -1,7 +1,7 @@ # # Makefile for optimizer # -# $PostgreSQL: pgsql/src/backend/optimizer/Makefile,v 1.14 2008/02/19 10:30:07 petere Exp $ +# src/backend/optimizer/Makefile # subdir = src/backend/optimizer diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index 7f84bf15de..aaa754cbb1 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/optimizer/README,v 1.53 2010/03/28 22:59:32 tgl Exp $ +src/backend/optimizer/README Optimizer ========= @@ -343,11 +343,13 @@ RelOptInfo - a relation or joined relations join clauses) Path - every way to generate a RelOptInfo(sequential,index,joins) - SeqScan - a plain Path node with pathtype = T_SeqScan - IndexPath - index scans + SeqScan - represents a sequential scan plan + IndexPath - index scan BitmapHeapPath - top of a bitmapped index scan TidPath - scan by CTID + ForeignPath - scan a foreign table AppendPath - append multiple subpaths together + MergeAppendPath - merge multiple subpaths, preserving their common sort order ResultPath - a Result plan node (used for FROM-less SELECT) MaterialPath - a Material plan node UniquePath - remove duplicate rows @@ -631,10 +633,39 @@ sort ordering was important; and so using the same PathKey for both sort orderings doesn't create any real problem. +Order of processing for EquivalenceClasses and PathKeys +------------------------------------------------------- + +As alluded to above, there is a specific sequence of phases in the +processing of EquivalenceClasses and PathKeys during planning. During the +initial scanning of the query's quals (deconstruct_jointree followed by +reconsider_outer_join_clauses), we construct EquivalenceClasses based on +mergejoinable clauses found in the quals. At the end of this process, +we know all we can know about equivalence of different variables, so +subsequently there will be no further merging of EquivalenceClasses. +At that point it is possible to consider the EquivalenceClasses as +"canonical" and build canonical PathKeys that reference them. Before +we reach that point (actually, before entering query_planner at all) +we also ensure that we have constructed EquivalenceClasses for all the +expressions used in the query's ORDER BY and related clauses. These +classes might or might not get merged together, depending on what we +find in the quals. + +Because all the EquivalenceClasses are known before we begin path +generation, we can use them as a guide to which indexes are of interest: +if an index's column is not mentioned in any EquivalenceClass then that +index's sort order cannot possibly be helpful for the query. This allows +short-circuiting of much of the processing of create_index_paths() for +irrelevant indexes. + +There are some cases where planner.c constructs additional +EquivalenceClasses and PathKeys after query_planner has completed. +In these cases, the extra ECs/PKs are needed to represent sort orders +that were not considered during query_planner. Such situations should be +minimized since it is impossible for query_planner to return a plan +producing such a sort order, meaning a explicit sort will always be needed. +Currently this happens only for queries involving multiple window functions +with different orderings, for which extra sorts are needed anyway. -Though Bob Devine <bob.devine@worldnet.att.net> was not involved in the -coding of our optimizer, he is available to field questions about -optimizer topics. -- bjm & tgl - diff --git a/src/backend/optimizer/geqo/Makefile b/src/backend/optimizer/geqo/Makefile index 9ccfd9e60c..3be7d7d450 100644 --- a/src/backend/optimizer/geqo/Makefile +++ b/src/backend/optimizer/geqo/Makefile @@ -5,7 +5,7 @@ # # Copyright (c) 1994, Regents of the University of California # -# $PostgreSQL: pgsql/src/backend/optimizer/geqo/Makefile,v 1.21 2009/07/16 20:55:44 tgl Exp $ +# src/backend/optimizer/geqo/Makefile # #------------------------------------------------------------------------- diff --git a/src/backend/optimizer/geqo/geqo_copy.c b/src/backend/optimizer/geqo/geqo_copy.c index 2a3b30232b..b6c0331fa0 100644 --- a/src/backend/optimizer/geqo/geqo_copy.c +++ b/src/backend/optimizer/geqo/geqo_copy.c @@ -2,10 +2,10 @@ * * geqo_copy.c * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_copy.c,v 1.21 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/geqo/geqo_copy.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_cx.c b/src/backend/optimizer/geqo/geqo_cx.c index 9f3dfe216e..afae948a61 100644 --- a/src/backend/optimizer/geqo/geqo_cx.c +++ b/src/backend/optimizer/geqo/geqo_cx.c @@ -6,7 +6,7 @@ * CX operator according to Oliver et al * (Proc 2nd Int'l Conf on GA's) * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_cx.c,v 1.11 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_cx.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_erx.c b/src/backend/optimizer/geqo/geqo_erx.c index 45effbdad9..69ac077616 100644 --- a/src/backend/optimizer/geqo/geqo_erx.c +++ b/src/backend/optimizer/geqo/geqo_erx.c @@ -3,7 +3,7 @@ * geqo_erx.c * edge recombination crossover [ER] * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_erx.c,v 1.21 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_erx.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index 353ed1aa1a..61caec4f85 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -3,10 +3,10 @@ * geqo_eval.c * Routines to evaluate query trees * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.93 2010/02/26 02:00:43 momjian Exp $ + * src/backend/optimizer/geqo/geqo_eval.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_main.c b/src/backend/optimizer/geqo/geqo_main.c index f66c18ceeb..e521bf7e3c 100644 --- a/src/backend/optimizer/geqo/geqo_main.c +++ b/src/backend/optimizer/geqo/geqo_main.c @@ -4,10 +4,10 @@ * solution to the query optimization problem * by means of a Genetic Algorithm (GA) * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_main.c,v 1.59 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/geqo/geqo_main.c * *------------------------------------------------------------------------- */ @@ -73,15 +73,17 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) Chromosome *kid; Pool *pool; int pool_size, - number_generations, - status_interval; + number_generations; + +#ifdef GEQO_DEBUG + int status_interval; +#endif Gene *best_tour; RelOptInfo *best_rel; #if defined(ERX) Edge *edge_table; /* list of edges */ int edge_failures = 0; - float difference; #endif #if defined(CX) || defined(PX) || defined(OX1) || defined(OX2) City *city_table; /* list of cities */ @@ -101,7 +103,9 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) /* set GA parameters */ pool_size = gimme_pool_size(number_of_rels); number_generations = gimme_number_generations(pool_size); +#ifdef GEQO_DEBUG status_interval = 10; +#endif /* allocate genetic pool memory */ pool = alloc_pool(root, pool_size, number_of_rels); @@ -178,7 +182,7 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) #if defined (ERX) /* EDGE RECOMBINATION CROSSOVER */ - difference = gimme_edge_table(root, momma->string, daddy->string, pool->string_length, edge_table); + gimme_edge_table(root, momma->string, daddy->string, pool->string_length, edge_table); kid = momma; diff --git a/src/backend/optimizer/geqo/geqo_misc.c b/src/backend/optimizer/geqo/geqo_misc.c index 70ad623abe..a00892d089 100644 --- a/src/backend/optimizer/geqo/geqo_misc.c +++ b/src/backend/optimizer/geqo/geqo_misc.c @@ -3,10 +3,10 @@ * geqo_misc.c * misc. printout and debug stuff * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_misc.c,v 1.50 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/geqo/geqo_misc.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_mutation.c b/src/backend/optimizer/geqo/geqo_mutation.c index bd527324f5..1a06d49775 100644 --- a/src/backend/optimizer/geqo/geqo_mutation.c +++ b/src/backend/optimizer/geqo/geqo_mutation.c @@ -4,7 +4,7 @@ * * TSP mutation routines * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_mutation.c,v 1.10 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_mutation.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_ox1.c b/src/backend/optimizer/geqo/geqo_ox1.c index d11d3cd9c3..fbf15282ad 100644 --- a/src/backend/optimizer/geqo/geqo_ox1.c +++ b/src/backend/optimizer/geqo/geqo_ox1.c @@ -6,7 +6,7 @@ * OX1 operator according to Davis * (Proc Int'l Joint Conf on AI) * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_ox1.c,v 1.10 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_ox1.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_ox2.c b/src/backend/optimizer/geqo/geqo_ox2.c index 48ed359a63..01c55bea41 100644 --- a/src/backend/optimizer/geqo/geqo_ox2.c +++ b/src/backend/optimizer/geqo/geqo_ox2.c @@ -6,7 +6,7 @@ * OX2 operator according to Syswerda * (The Genetic Algorithms Handbook, ed L Davis) * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_ox2.c,v 1.11 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_ox2.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_pmx.c b/src/backend/optimizer/geqo/geqo_pmx.c index a1f3d1019e..deb0f7b353 100644 --- a/src/backend/optimizer/geqo/geqo_pmx.c +++ b/src/backend/optimizer/geqo/geqo_pmx.c @@ -6,7 +6,7 @@ * PMX operator according to Goldberg & Lingle * (Proc Int'l Conf on GA's) * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_pmx.c,v 1.11 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_pmx.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_pool.c b/src/backend/optimizer/geqo/geqo_pool.c index 372096818b..d2bbdce804 100644 --- a/src/backend/optimizer/geqo/geqo_pool.c +++ b/src/backend/optimizer/geqo/geqo_pool.c @@ -3,10 +3,10 @@ * geqo_pool.c * Genetic Algorithm (GA) pool stuff * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_pool.c,v 1.36 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/geqo/geqo_pool.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_px.c b/src/backend/optimizer/geqo/geqo_px.c index 674529313a..808ff6a14c 100644 --- a/src/backend/optimizer/geqo/geqo_px.c +++ b/src/backend/optimizer/geqo/geqo_px.c @@ -6,7 +6,7 @@ * PX operator according to Syswerda * (The Genetic Algorithms Handbook, L Davis, ed) * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_px.c,v 1.11 2009/07/16 20:55:44 tgl Exp $ +* src/backend/optimizer/geqo/geqo_px.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_random.c b/src/backend/optimizer/geqo/geqo_random.c index 9ff5b40ecd..6d04c8ec2f 100644 --- a/src/backend/optimizer/geqo/geqo_random.c +++ b/src/backend/optimizer/geqo/geqo_random.c @@ -3,10 +3,10 @@ * geqo_random.c * random number generator * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_random.c,v 1.3 2010/02/26 02:00:44 momjian Exp $ + * src/backend/optimizer/geqo/geqo_random.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_recombination.c b/src/backend/optimizer/geqo/geqo_recombination.c index b31c7634b0..652fadc745 100644 --- a/src/backend/optimizer/geqo/geqo_recombination.c +++ b/src/backend/optimizer/geqo/geqo_recombination.c @@ -3,7 +3,7 @@ * geqo_recombination.c * misc recombination procedures * -* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_recombination.c,v 1.17 2009/07/19 21:00:43 tgl Exp $ +* src/backend/optimizer/geqo/geqo_recombination.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/geqo/geqo_selection.c b/src/backend/optimizer/geqo/geqo_selection.c index c727b90680..af70c735df 100644 --- a/src/backend/optimizer/geqo/geqo_selection.c +++ b/src/backend/optimizer/geqo/geqo_selection.c @@ -3,10 +3,10 @@ * geqo_selection.c * linear selection scheme for the genetic query optimizer * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_selection.c,v 1.27 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/geqo/geqo_selection.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile index 77be4e4d17..07938dbe57 100644 --- a/src/backend/optimizer/path/Makefile +++ b/src/backend/optimizer/path/Makefile @@ -4,7 +4,7 @@ # Makefile for optimizer/path # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/optimizer/path/Makefile,v 1.19 2008/02/19 10:30:07 petere Exp $ +# src/backend/optimizer/path/Makefile # #------------------------------------------------------------------------- diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index d5acb48bb1..aa3403a1ea 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3,12 +3,12 @@ * allpaths.c * Routines to find possible search paths for processing a query * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.194 2010/03/28 22:59:32 tgl Exp $ + * src/backend/optimizer/path/allpaths.c * *------------------------------------------------------------------------- */ @@ -18,6 +18,7 @@ #include <math.h> #include "catalog/pg_namespace.h" +#include "catalog/pg_class.h" #include "nodes/nodeFuncs.h" #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" @@ -58,6 +59,7 @@ static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static List *accumulate_append_subpath(List *subpaths, Path *path); static void set_dummy_rel_pathlist(RelOptInfo *rel); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); @@ -69,6 +71,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, bool *differentTypes); @@ -178,34 +182,45 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, /* It's an "append relation", process accordingly */ set_append_rel_pathlist(root, rel, rti, rte); } - else if (rel->rtekind == RTE_SUBQUERY) - { - /* Subquery --- generate a separate plan for it */ - set_subquery_pathlist(root, rel, rti, rte); - } - else if (rel->rtekind == RTE_FUNCTION) - { - /* RangeFunction --- generate a suitable path for it */ - set_function_pathlist(root, rel, rte); - } - else if (rel->rtekind == RTE_VALUES) - { - /* Values list --- generate a suitable path for it */ - set_values_pathlist(root, rel, rte); - } - else if (rel->rtekind == RTE_CTE) - { - /* CTE reference --- generate a suitable path for it */ - if (rte->self_reference) - set_worktable_pathlist(root, rel, rte); - else - set_cte_pathlist(root, rel, rte); - } else { - /* Plain relation */ - Assert(rel->rtekind == RTE_RELATION); - set_plain_rel_pathlist(root, rel, rte); + switch (rel->rtekind) + { + case RTE_RELATION: + if (rte->relkind == RELKIND_FOREIGN_TABLE) + { + /* Foreign table */ + set_foreign_pathlist(root, rel, rte); + } + else + { + /* Plain relation */ + set_plain_rel_pathlist(root, rel, rte); + } + break; + case RTE_SUBQUERY: + /* Subquery --- generate a separate plan for it */ + set_subquery_pathlist(root, rel, rti, rte); + break; + case RTE_FUNCTION: + /* RangeFunction --- generate a suitable path for it */ + set_function_pathlist(root, rel, rte); + break; + case RTE_VALUES: + /* Values list --- generate a suitable path for it */ + set_values_pathlist(root, rel, rte); + break; + case RTE_CTE: + /* CTE reference --- generate a suitable path for it */ + if (rte->self_reference) + set_worktable_pathlist(root, rel, rte); + else + set_cte_pathlist(root, rel, rte); + break; + default: + elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); + break; + } } #ifdef OPTIMIZER_DEBUG @@ -308,7 +323,9 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { int parentRTindex = rti; + List *live_childrels = NIL; List *subpaths = NIL; + List *all_child_pathkeys = NIL; double parent_rows; double parent_size; double *parent_attrsizes; @@ -346,7 +363,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; List *childquals; Node *childqual; - Path *childpath; + ListCell *lcp; ListCell *parentvars; ListCell *childvars; @@ -420,13 +437,15 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, /* * We have to make child entries in the EquivalenceClass data - * structures as well. + * structures as well. This is needed either if the parent + * participates in some eclass joins (because we will want to consider + * inner-indexscan joins on the individual children) or if the parent + * has useful pathkeys (because we should try to build MergeAppend + * paths that produce those sort orderings). */ - if (rel->has_eclass_joins) - { + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) add_child_rel_equivalences(root, appinfo, rel, childrel); - childrel->has_eclass_joins = true; - } + childrel->has_eclass_joins = rel->has_eclass_joins; /* * Note: we could compute appropriate attr_needed data for the child's @@ -436,23 +455,52 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, * otherrels. So we just leave the child's attr_needed empty. */ + /* Remember which childrels are live, for MergeAppend logic below */ + live_childrels = lappend(live_childrels, childrel); + /* * Compute the child's access paths, and add the cheapest one to the * Append path we are constructing for the parent. - * - * It's possible that the child is itself an appendrel, in which case - * we can "cut out the middleman" and just add its child paths to our - * own list. (We don't try to do this earlier because we need to - * apply both levels of transformation to the quals.) */ set_rel_pathlist(root, childrel, childRTindex, childRTE); - childpath = childrel->cheapest_total_path; - if (IsA(childpath, AppendPath)) - subpaths = list_concat(subpaths, - ((AppendPath *) childpath)->subpaths); - else - subpaths = lappend(subpaths, childpath); + subpaths = accumulate_append_subpath(subpaths, + childrel->cheapest_total_path); + + /* + * Collect a list of all the available path orderings for all the + * children. We use this as a heuristic to indicate which sort + * orderings we should build MergeAppend paths for. + */ + foreach(lcp, childrel->pathlist) + { + Path *childpath = (Path *) lfirst(lcp); + List *childkeys = childpath->pathkeys; + ListCell *lpk; + bool found = false; + + /* Ignore unsorted paths */ + if (childkeys == NIL) + continue; + + /* Have we already seen this ordering? */ + foreach(lpk, all_child_pathkeys) + { + List *existing_pathkeys = (List *) lfirst(lpk); + + if (compare_pathkeys(existing_pathkeys, + childkeys) == PATHKEYS_EQUAL) + { + found = true; + break; + } + } + if (!found) + { + /* No, so add it to all_child_pathkeys */ + all_child_pathkeys = lappend(all_child_pathkeys, childkeys); + } + } /* * Accumulate size information from each child. @@ -508,17 +556,107 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, pfree(parent_attrsizes); /* - * Finally, build Append path and install it as the only access path for - * the parent rel. (Note: this is correct even if we have zero or one - * live subpath due to constraint exclusion.) + * Next, build an unordered Append path for the rel. (Note: this is + * correct even if we have zero or one live subpath due to constraint + * exclusion.) */ add_path(rel, (Path *) create_append_path(rel, subpaths)); - /* Select cheapest path (pretty easy in this case...) */ + /* + * Next, build MergeAppend paths based on the collected list of child + * pathkeys. We consider both cheapest-startup and cheapest-total cases, + * ie, for each interesting ordering, collect all the cheapest startup + * subpaths and all the cheapest total paths, and build a MergeAppend path + * for each list. + */ + foreach(l, all_child_pathkeys) + { + List *pathkeys = (List *) lfirst(l); + List *startup_subpaths = NIL; + List *total_subpaths = NIL; + bool startup_neq_total = false; + ListCell *lcr; + + /* Select the child paths for this ordering... */ + foreach(lcr, live_childrels) + { + RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr); + Path *cheapest_startup, + *cheapest_total; + + /* Locate the right paths, if they are available. */ + cheapest_startup = + get_cheapest_path_for_pathkeys(childrel->pathlist, + pathkeys, + STARTUP_COST); + cheapest_total = + get_cheapest_path_for_pathkeys(childrel->pathlist, + pathkeys, + TOTAL_COST); + + /* + * If we can't find any paths with the right order just add the + * cheapest-total path; we'll have to sort it. + */ + if (cheapest_startup == NULL) + cheapest_startup = childrel->cheapest_total_path; + if (cheapest_total == NULL) + cheapest_total = childrel->cheapest_total_path; + + /* + * Notice whether we actually have different paths for the + * "cheapest" and "total" cases; frequently there will be no point + * in two create_merge_append_path() calls. + */ + if (cheapest_startup != cheapest_total) + startup_neq_total = true; + + startup_subpaths = + accumulate_append_subpath(startup_subpaths, cheapest_startup); + total_subpaths = + accumulate_append_subpath(total_subpaths, cheapest_total); + } + + /* ... and build the MergeAppend paths */ + add_path(rel, (Path *) create_merge_append_path(root, + rel, + startup_subpaths, + pathkeys)); + if (startup_neq_total) + add_path(rel, (Path *) create_merge_append_path(root, + rel, + total_subpaths, + pathkeys)); + } + + /* Select cheapest path */ set_cheapest(rel); } /* + * accumulate_append_subpath + * Add a subpath to the list being built for an Append or MergeAppend + * + * It's possible that the child is itself an Append path, in which case + * we can "cut out the middleman" and just add its child paths to our + * own list. (We don't try to do this earlier because we need to + * apply both levels of transformation to the quals.) + */ +static List * +accumulate_append_subpath(List *subpaths, Path *path) +{ + if (IsA(path, AppendPath)) + { + AppendPath *apath = (AppendPath *) path; + + /* list_copy is important here to avoid sharing list substructure */ + return list_concat(subpaths, list_copy(apath->subpaths)); + } + else + return lappend(subpaths, path); +} + +/* * set_dummy_rel_pathlist * Build a dummy path for a relation that's been excluded by constraints * @@ -659,11 +797,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, rel->subrtable = subroot->parse->rtable; rel->subrowmark = subroot->rowMarks; - /* Copy number of output rows from subplan */ - rel->tuples = rel->subplan->plan_rows; - /* Mark rel with estimated output rows, width, etc */ - set_baserel_size_estimates(root, rel); + set_subquery_size_estimates(root, rel, subroot); /* Convert subquery pathkeys to outer representation */ pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys); @@ -809,6 +944,23 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_foreign_pathlist + * Build the (single) access path for a foreign table RTE + */ +static void +set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +{ + /* Mark rel with estimated output rows, width, etc */ + set_foreign_size_estimates(root, rel); + + /* Generate appropriate path */ + add_path(rel, (Path *) create_foreignscan_path(root, rel)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(rel); +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * @@ -1407,9 +1559,15 @@ print_path(PlannerInfo *root, Path *path, int indent) case T_TidPath: ptype = "TidScan"; break; + case T_ForeignPath: + ptype = "ForeignScan"; + break; case T_AppendPath: ptype = "Append"; break; + case T_MergeAppendPath: + ptype = "MergeAppend"; + break; case T_ResultPath: ptype = "Result"; break; diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index c4bc1ee7e4..ca068991c1 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -3,12 +3,12 @@ * clausesel.c * Routines to compute clause selectivities * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.99 2010/01/02 16:57:46 momjian Exp $ + * src/backend/optimizer/path/clausesel.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 8cb23bcd64..45a4f39169 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -55,11 +55,11 @@ * the non-cost fields of the passed XXXPath to be filled in. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.218 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/path/costsize.c * *------------------------------------------------------------------------- */ @@ -76,6 +76,7 @@ #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/placeholder.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/restrictinfo.h" #include "parser/parsetree.h" @@ -211,6 +212,7 @@ cost_seqscan(Path *path, PlannerInfo *root, * * 'index' is the index to be used * 'indexQuals' is the list of applicable qual clauses (implicit AND semantics) + * 'indexOrderBys' is the list of ORDER BY operators for amcanorderbyop indexes * 'outer_rel' is the outer relation when we are considering using the index * scan as the inside of a nestloop join (hence, some of the indexQuals * are join clauses, and we should expect repeated scans of the index); @@ -220,18 +222,19 @@ cost_seqscan(Path *path, PlannerInfo *root, * additional fields of the IndexPath besides startup_cost and total_cost. * These fields are needed if the IndexPath is used in a BitmapIndexScan. * + * indexQuals is a list of RestrictInfo nodes, but indexOrderBys is a list of + * bare expressions. + * * NOTE: 'indexQuals' must contain only clauses usable as index restrictions. * Any additional quals evaluated as qpquals may reduce the number of returned * tuples, but they won't reduce the number of tuples we have to fetch from * the table, so they don't reduce the scan cost. - * - * NOTE: as of 8.0, indexQuals is a list of RestrictInfo nodes, where formerly - * it was a list of bare clause expressions. */ void cost_index(IndexPath *path, PlannerInfo *root, IndexOptInfo *index, List *indexQuals, + List *indexOrderBys, RelOptInfo *outer_rel) { RelOptInfo *baserel = index->rel; @@ -265,10 +268,11 @@ cost_index(IndexPath *path, PlannerInfo *root, * the fraction of main-table tuples we will have to retrieve) and its * correlation to the main-table tuple order. */ - OidFunctionCall8(index->amcostestimate, + OidFunctionCall9(index->amcostestimate, PointerGetDatum(root), PointerGetDatum(index), PointerGetDatum(indexQuals), + PointerGetDatum(indexOrderBys), PointerGetDatum(outer_rel), PointerGetDatum(&indexStartupCost), PointerGetDatum(&indexTotalCost), @@ -1074,33 +1078,37 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) * Determines and returns the cost of sorting a relation, including * the cost of reading the input data. * - * If the total volume of data to sort is less than work_mem, we will do + * If the total volume of data to sort is less than sort_mem, we will do * an in-memory sort, which requires no I/O and about t*log2(t) tuple * comparisons for t tuples. * - * If the total volume exceeds work_mem, we switch to a tape-style merge + * If the total volume exceeds sort_mem, we switch to a tape-style merge * algorithm. There will still be about t*log2(t) tuple comparisons in * total, but we will also need to write and read each tuple once per * merge pass. We expect about ceil(logM(r)) merge passes where r is the * number of initial runs formed and M is the merge order used by tuplesort.c. - * Since the average initial run should be about twice work_mem, we have - * disk traffic = 2 * relsize * ceil(logM(p / (2*work_mem))) + * Since the average initial run should be about twice sort_mem, we have + * disk traffic = 2 * relsize * ceil(logM(p / (2*sort_mem))) * cpu = comparison_cost * t * log2(t) * * If the sort is bounded (i.e., only the first k result tuples are needed) - * and k tuples can fit into work_mem, we use a heap method that keeps only + * and k tuples can fit into sort_mem, we use a heap method that keeps only * k tuples in the heap; this will require about t*log2(k) tuple comparisons. * * The disk traffic is assumed to be 3/4ths sequential and 1/4th random * accesses (XXX can't we refine that guess?) * - * We charge two operator evals per tuple comparison, which should be in - * the right ballpark in most cases. + * By default, we charge two operator evals per tuple comparison, which should + * be in the right ballpark in most cases. The caller can tweak this by + * specifying nonzero comparison_cost; typically that's used for any extra + * work that has to be done to prepare the inputs to the comparison operators. * * 'pathkeys' is a list of sort keys * 'input_cost' is the total cost for reading the input data * 'tuples' is the number of tuples in the relation * 'width' is the average tuple width in bytes + * 'comparison_cost' is the extra cost per comparison, if any + * 'sort_mem' is the number of kilobytes of work memory allowed for the sort * 'limit_tuples' is the bound on the number of output tuples; -1 if no bound * * NOTE: some callers currently pass NIL for pathkeys because they @@ -1113,6 +1121,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) void cost_sort(Path *path, PlannerInfo *root, List *pathkeys, Cost input_cost, double tuples, int width, + Cost comparison_cost, int sort_mem, double limit_tuples) { Cost startup_cost = input_cost; @@ -1120,7 +1129,7 @@ cost_sort(Path *path, PlannerInfo *root, double input_bytes = relation_byte_size(tuples, width); double output_bytes; double output_tuples; - long work_mem_bytes = work_mem * 1024L; + long sort_mem_bytes = sort_mem * 1024L; if (!enable_sort) startup_cost += disable_cost; @@ -1132,6 +1141,9 @@ cost_sort(Path *path, PlannerInfo *root, if (tuples < 2.0) tuples = 2.0; + /* Include the default cost-per-comparison */ + comparison_cost += 2.0 * cpu_operator_cost; + /* Do we have a useful LIMIT? */ if (limit_tuples > 0 && limit_tuples < tuples) { @@ -1144,24 +1156,23 @@ cost_sort(Path *path, PlannerInfo *root, output_bytes = input_bytes; } - if (output_bytes > work_mem_bytes) + if (output_bytes > sort_mem_bytes) { /* * We'll have to use a disk-based sort of all the tuples */ double npages = ceil(input_bytes / BLCKSZ); - double nruns = (input_bytes / work_mem_bytes) * 0.5; - double mergeorder = tuplesort_merge_order(work_mem_bytes); + double nruns = (input_bytes / sort_mem_bytes) * 0.5; + double mergeorder = tuplesort_merge_order(sort_mem_bytes); double log_runs; double npageaccesses; /* * CPU costs * - * Assume about two operator evals per tuple comparison and N log2 N - * comparisons + * Assume about N log2 N comparisons */ - startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(tuples); + startup_cost += comparison_cost * tuples * LOG2(tuples); /* Disk costs */ @@ -1175,7 +1186,7 @@ cost_sort(Path *path, PlannerInfo *root, startup_cost += npageaccesses * (seq_page_cost * 0.75 + random_page_cost * 0.25); } - else if (tuples > 2 * output_tuples || input_bytes > work_mem_bytes) + else if (tuples > 2 * output_tuples || input_bytes > sort_mem_bytes) { /* * We'll use a bounded heap-sort keeping just K tuples in memory, for @@ -1183,12 +1194,12 @@ cost_sort(Path *path, PlannerInfo *root, * factor is a bit higher than for quicksort. Tweak it so that the * cost curve is continuous at the crossover point. */ - startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(2.0 * output_tuples); + startup_cost += comparison_cost * tuples * LOG2(2.0 * output_tuples); } else { /* We'll use plain quicksort on all the input tuples */ - startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(tuples); + startup_cost += comparison_cost * tuples * LOG2(tuples); } /* @@ -1206,6 +1217,70 @@ cost_sort(Path *path, PlannerInfo *root, } /* + * cost_merge_append + * Determines and returns the cost of a MergeAppend node. + * + * MergeAppend merges several pre-sorted input streams, using a heap that + * at any given instant holds the next tuple from each stream. If there + * are N streams, we need about N*log2(N) tuple comparisons to construct + * the heap at startup, and then for each output tuple, about log2(N) + * comparisons to delete the top heap entry and another log2(N) comparisons + * to insert its successor from the same stream. + * + * (The effective value of N will drop once some of the input streams are + * exhausted, but it seems unlikely to be worth trying to account for that.) + * + * The heap is never spilled to disk, since we assume N is not very large. + * So this is much simpler than cost_sort. + * + * As in cost_sort, we charge two operator evals per tuple comparison. + * + * 'pathkeys' is a list of sort keys + * 'n_streams' is the number of input streams + * 'input_startup_cost' is the sum of the input streams' startup costs + * 'input_total_cost' is the sum of the input streams' total costs + * 'tuples' is the number of tuples in all the streams + */ +void +cost_merge_append(Path *path, PlannerInfo *root, + List *pathkeys, int n_streams, + Cost input_startup_cost, Cost input_total_cost, + double tuples) +{ + Cost startup_cost = 0; + Cost run_cost = 0; + Cost comparison_cost; + double N; + double logN; + + /* + * Avoid log(0)... + */ + N = (n_streams < 2) ? 2.0 : (double) n_streams; + logN = LOG2(N); + + /* Assumed cost per tuple comparison */ + comparison_cost = 2.0 * cpu_operator_cost; + + /* Heap creation cost */ + startup_cost += comparison_cost * N * logN; + + /* Per-tuple heap maintenance cost */ + run_cost += tuples * comparison_cost * 2.0 * logN; + + /* + * Also charge a small amount (arbitrarily set equal to operator cost) per + * extracted tuple. We don't charge cpu_tuple_cost because a MergeAppend + * node doesn't do qual-checking or projection, so it has less overhead + * than most plan nodes. + */ + run_cost += cpu_operator_cost * tuples; + + path->startup_cost = startup_cost + input_startup_cost; + path->total_cost = startup_cost + run_cost + input_total_cost; +} + +/* * cost_material * Determines and returns the cost of materializing a relation, including * the cost of reading the input data. @@ -1263,25 +1338,40 @@ cost_material(Path *path, * Determines and returns the cost of performing an Agg plan node, * including the cost of its input. * + * aggcosts can be NULL when there are no actual aggregate functions (i.e., + * we are using a hashed Agg node just to do grouping). + * * Note: when aggstrategy == AGG_SORTED, caller must ensure that input costs * are for appropriately-sorted input. */ void cost_agg(Path *path, PlannerInfo *root, - AggStrategy aggstrategy, int numAggs, + AggStrategy aggstrategy, const AggClauseCosts *aggcosts, int numGroupCols, double numGroups, Cost input_startup_cost, Cost input_total_cost, double input_tuples) { Cost startup_cost; Cost total_cost; + AggClauseCosts dummy_aggcosts; + + /* Use all-zero per-aggregate costs if NULL is passed */ + if (aggcosts == NULL) + { + Assert(aggstrategy == AGG_HASHED); + MemSet(&dummy_aggcosts, 0, sizeof(AggClauseCosts)); + aggcosts = &dummy_aggcosts; + } /* - * We charge one cpu_operator_cost per aggregate function per input tuple, - * and another one per output tuple (corresponding to transfn and finalfn - * calls respectively). If we are grouping, we charge an additional - * cpu_operator_cost per grouping column per input tuple for grouping - * comparisons. + * The transCost.per_tuple component of aggcosts should be charged once + * per input tuple, corresponding to the costs of evaluating the aggregate + * transfns and their input expressions (with any startup cost of course + * charged but once). The finalCost component is charged once per output + * tuple, corresponding to the costs of evaluating the finalfns. + * + * If we are grouping, we charge an additional cpu_operator_cost per + * grouping column per input tuple for grouping comparisons. * * We will produce a single output tuple if not grouping, and a tuple per * group otherwise. We charge cpu_tuple_cost for each output tuple. @@ -1294,15 +1384,13 @@ cost_agg(Path *path, PlannerInfo *root, * there's roundoff error we might do the wrong thing. So be sure that * the computations below form the same intermediate values in the same * order. - * - * Note: ideally we should use the pg_proc.procost costs of each - * aggregate's component functions, but for now that seems like an - * excessive amount of work. */ if (aggstrategy == AGG_PLAIN) { startup_cost = input_total_cost; - startup_cost += cpu_operator_cost * (input_tuples + 1) * numAggs; + startup_cost += aggcosts->transCost.startup; + startup_cost += aggcosts->transCost.per_tuple * input_tuples; + startup_cost += aggcosts->finalCost; /* we aren't grouping */ total_cost = startup_cost + cpu_tuple_cost; } @@ -1312,19 +1400,21 @@ cost_agg(Path *path, PlannerInfo *root, startup_cost = input_startup_cost; total_cost = input_total_cost; /* calcs phrased this way to match HASHED case, see note above */ - total_cost += cpu_operator_cost * input_tuples * numGroupCols; - total_cost += cpu_operator_cost * input_tuples * numAggs; - total_cost += cpu_operator_cost * numGroups * numAggs; + total_cost += aggcosts->transCost.startup; + total_cost += aggcosts->transCost.per_tuple * input_tuples; + total_cost += (cpu_operator_cost * numGroupCols) * input_tuples; + total_cost += aggcosts->finalCost * numGroups; total_cost += cpu_tuple_cost * numGroups; } else { /* must be AGG_HASHED */ startup_cost = input_total_cost; - startup_cost += cpu_operator_cost * input_tuples * numGroupCols; - startup_cost += cpu_operator_cost * input_tuples * numAggs; + startup_cost += aggcosts->transCost.startup; + startup_cost += aggcosts->transCost.per_tuple * input_tuples; + startup_cost += (cpu_operator_cost * numGroupCols) * input_tuples; total_cost = startup_cost; - total_cost += cpu_operator_cost * numGroups * numAggs; + total_cost += aggcosts->finalCost * numGroups; total_cost += cpu_tuple_cost * numGroups; } @@ -1341,25 +1431,53 @@ cost_agg(Path *path, PlannerInfo *root, */ void cost_windowagg(Path *path, PlannerInfo *root, - int numWindowFuncs, int numPartCols, int numOrderCols, + List *windowFuncs, int numPartCols, int numOrderCols, Cost input_startup_cost, Cost input_total_cost, double input_tuples) { Cost startup_cost; Cost total_cost; + ListCell *lc; startup_cost = input_startup_cost; total_cost = input_total_cost; /* - * We charge one cpu_operator_cost per window function per tuple (often a - * drastic underestimate, but without a way to gauge how many tuples the - * window function will fetch, it's hard to do better). We also charge - * cpu_operator_cost per grouping column per tuple for grouping - * comparisons, plus cpu_tuple_cost per tuple for general overhead. - */ - total_cost += cpu_operator_cost * input_tuples * numWindowFuncs; - total_cost += cpu_operator_cost * input_tuples * (numPartCols + numOrderCols); + * Window functions are assumed to cost their stated execution cost, plus + * the cost of evaluating their input expressions, per tuple. Since they + * may in fact evaluate their inputs at multiple rows during each cycle, + * this could be a drastic underestimate; but without a way to know how + * many rows the window function will fetch, it's hard to do better. In + * any case, it's a good estimate for all the built-in window functions, + * so we'll just do this for now. + */ + foreach(lc, windowFuncs) + { + WindowFunc *wfunc = (WindowFunc *) lfirst(lc); + Cost wfunccost; + QualCost argcosts; + + Assert(IsA(wfunc, WindowFunc)); + + wfunccost = get_func_cost(wfunc->winfnoid) * cpu_operator_cost; + + /* also add the input expressions' cost to per-input-row costs */ + cost_qual_eval_node(&argcosts, (Node *) wfunc->args, root); + startup_cost += argcosts.startup; + wfunccost += argcosts.per_tuple; + + total_cost += wfunccost * input_tuples; + } + + /* + * We also charge cpu_operator_cost per grouping column per tuple for + * grouping comparisons, plus cpu_tuple_cost per tuple for general + * overhead. + * + * XXX this neglects costs of spooling the data to disk when it overflows + * work_mem. Sooner or later that should get accounted for. + */ + total_cost += cpu_operator_cost * (numPartCols + numOrderCols) * input_tuples; total_cost += cpu_tuple_cost * input_tuples; path->startup_cost = startup_cost; @@ -1401,7 +1519,9 @@ cost_group(Path *path, PlannerInfo *root, * output row count, which may be lower than the restriction-clause-only row * count of its parent. (We don't include this case in the PATH_ROWS macro * because it applies *only* to a nestloop's inner relation.) We have to - * be prepared to recurse through Append nodes in case of an appendrel. + * be prepared to recurse through Append or MergeAppend nodes in case of an + * appendrel. (It's not clear MergeAppend can be seen here, but we may as + * well handle it if so.) */ static double nestloop_inner_path_rows(Path *path) @@ -1422,6 +1542,16 @@ nestloop_inner_path_rows(Path *path) result += nestloop_inner_path_rows((Path *) lfirst(l)); } } + else if (IsA(path, MergeAppendPath)) + { + ListCell *l; + + result = 0; + foreach(l, ((MergeAppendPath *) path)->subpaths) + { + result += nestloop_inner_path_rows((Path *) lfirst(l)); + } + } else result = PATH_ROWS(path); @@ -1620,10 +1750,10 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) innerendsel; Path sort_path; /* dummy for result of cost_sort */ - /* Protect some assumptions below that rowcounts aren't zero */ - if (outer_path_rows <= 0) + /* Protect some assumptions below that rowcounts aren't zero or NaN */ + if (outer_path_rows <= 0 || isnan(outer_path_rows)) outer_path_rows = 1; - if (inner_path_rows <= 0) + if (inner_path_rows <= 0 || isnan(inner_path_rows)) inner_path_rows = 1; if (!enable_mergejoin) @@ -1711,6 +1841,7 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) ipathkey = (PathKey *) linitial(ipathkeys); /* debugging check */ if (opathkey->pk_opfamily != ipathkey->pk_opfamily || + opathkey->pk_eclass->ec_collation != ipathkey->pk_eclass->ec_collation || opathkey->pk_strategy != ipathkey->pk_strategy || opathkey->pk_nulls_first != ipathkey->pk_nulls_first) elog(ERROR, "left and right pathkeys do not match in mergejoin"); @@ -1789,6 +1920,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) outer_path->total_cost, outer_path_rows, outer_path->parent->width, + 0.0, + work_mem, -1.0); startup_cost += sort_path.startup_cost; startup_cost += (sort_path.total_cost - sort_path.startup_cost) @@ -1813,6 +1946,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) inner_path->total_cost, inner_path_rows, inner_path->parent->width, + 0.0, + work_mem, -1.0); startup_cost += sort_path.startup_cost; startup_cost += (sort_path.total_cost - sort_path.startup_cost) @@ -1957,6 +2092,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) { cache = (MergeScanSelCache *) lfirst(lc); if (cache->opfamily == pathkey->pk_opfamily && + cache->collation == pathkey->pk_eclass->ec_collation && cache->strategy == pathkey->pk_strategy && cache->nulls_first == pathkey->pk_nulls_first) return cache; @@ -1978,6 +2114,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) cache = (MergeScanSelCache *) palloc(sizeof(MergeScanSelCache)); cache->opfamily = pathkey->pk_opfamily; + cache->collation = pathkey->pk_eclass->ec_collation; cache->strategy = pathkey->pk_strategy; cache->nulls_first = pathkey->pk_nulls_first; cache->leftstartsel = leftstartsel; @@ -2549,17 +2686,12 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) * Vars and Consts are charged zero, and so are boolean operators (AND, * OR, NOT). Simplistic, but a lot better than no model at all. * - * Note that Aggref and WindowFunc nodes are (and should be) treated like - * Vars --- whatever execution cost they have is absorbed into - * plan-node-specific costing. As far as expression evaluation is - * concerned they're just like Vars. - * * Should we try to account for the possibility of short-circuit * evaluation of AND/OR? Probably *not*, because that would make the * results depend on the clause ordering, and we are not in any position * to expect that the current ordering of the clauses is the one that's - * going to end up being used. (Is it worth applying order_qual_clauses - * much earlier in the planning process to fix this?) + * going to end up being used. The above per-RestrictInfo caching would + * not mix well with trying to re-order clauses anyway. */ if (IsA(node, FuncExpr)) { @@ -2588,6 +2720,20 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) context->total.per_tuple += get_func_cost(saop->opfuncid) * cpu_operator_cost * estimate_array_length(arraynode) * 0.5; } + else if (IsA(node, Aggref) || + IsA(node, WindowFunc)) + { + /* + * Aggref and WindowFunc nodes are (and should be) treated like Vars, + * ie, zero execution cost in the current model, because they behave + * essentially like Vars in execQual.c. We disregard the costs of + * their input expressions for the same reason. The actual execution + * costs of the aggregate/window functions and their arguments have to + * be factored into plan-node-specific costing of the Agg or WindowAgg + * plan node. + */ + return false; /* don't recurse into children */ + } else if (IsA(node, CoerceViaIO)) { CoerceViaIO *iocoerce = (CoerceViaIO *) node; @@ -2813,12 +2959,20 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, */ if (indexed_join_quals) { - List *nrclauses; + if (path->joinrestrictinfo != NIL) + { + List *nrclauses; - nrclauses = select_nonredundant_join_clauses(root, - path->joinrestrictinfo, - path->innerjoinpath); - *indexed_join_quals = (nrclauses == NIL); + nrclauses = select_nonredundant_join_clauses(root, + path->joinrestrictinfo, + path->innerjoinpath); + *indexed_join_quals = (nrclauses == NIL); + } + else + { + /* a clauseless join does NOT qualify */ + *indexed_join_quals = false; + } } return true; @@ -2894,7 +3048,7 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals) * Set the size estimates for the given base relation. * * The rel's targetlist and restrictinfo list must have been constructed - * already. + * already, and rel->tuples must be set. * * We set the following fields of the rel node: * rows: the estimated number of output tuples (after applying @@ -3060,6 +3214,76 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, } /* + * set_subquery_size_estimates + * Set the size estimates for a base relation that is a subquery. + * + * The rel's targetlist and restrictinfo list must have been constructed + * already, and the plan for the subquery must have been completed. + * We look at the subquery's plan and PlannerInfo to extract data. + * + * We set the same fields as set_baserel_size_estimates. + */ +void +set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel, + PlannerInfo *subroot) +{ + RangeTblEntry *rte; + ListCell *lc; + + /* Should only be applied to base relations that are subqueries */ + Assert(rel->relid > 0); + rte = planner_rt_fetch(rel->relid, root); + Assert(rte->rtekind == RTE_SUBQUERY); + + /* Copy raw number of output rows from subplan */ + rel->tuples = rel->subplan->plan_rows; + + /* + * Compute per-output-column width estimates by examining the subquery's + * targetlist. For any output that is a plain Var, get the width estimate + * that was made while planning the subquery. Otherwise, fall back on a + * datatype-based estimate. + */ + foreach(lc, subroot->parse->targetList) + { + TargetEntry *te = (TargetEntry *) lfirst(lc); + Node *texpr = (Node *) te->expr; + int32 item_width; + + Assert(IsA(te, TargetEntry)); + /* junk columns aren't visible to upper query */ + if (te->resjunk) + continue; + + /* + * XXX This currently doesn't work for subqueries containing set + * operations, because the Vars in their tlists are bogus references + * to the first leaf subquery, which wouldn't give the right answer + * even if we could still get to its PlannerInfo. So fall back on + * datatype in that case. + */ + if (IsA(texpr, Var) && + subroot->parse->setOperations == NULL) + { + Var *var = (Var *) texpr; + RelOptInfo *subrel = find_base_rel(subroot, var->varno); + + item_width = subrel->attr_widths[var->varattno - subrel->min_attr]; + } + else + { + item_width = get_typavgwidth(exprType(texpr), exprTypmod(texpr)); + } + Assert(item_width > 0); + Assert(te->resno >= rel->min_attr && te->resno <= rel->max_attr); + rel->attr_widths[te->resno - rel->min_attr] = item_width; + } + + /* Now estimate number of output rows, etc */ + set_baserel_size_estimates(root, rel); +} + +/* * set_function_size_estimates * Set the size estimates for a base relation that is a function call. * @@ -3154,16 +3378,50 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan) set_baserel_size_estimates(root, rel); } +/* + * set_foreign_size_estimates + * Set the size estimates for a base relation that is a foreign table. + * + * There is not a whole lot that we can do here; the foreign-data wrapper + * is responsible for producing useful estimates. We can do a decent job + * of estimating baserestrictcost, so we set that, and we also set up width + * using what will be purely datatype-driven estimates from the targetlist. + * There is no way to do anything sane with the rows value, so we just put + * a default estimate and hope that the wrapper can improve on it. The + * wrapper's PlanForeignScan function will be called momentarily. + * + * The rel's targetlist and restrictinfo list must have been constructed + * already. + */ +void +set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel) +{ + /* Should only be applied to base relations */ + Assert(rel->relid > 0); + + rel->rows = 1000; /* entirely bogus default estimate */ + + cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root); + + set_rel_width(root, rel); +} + /* * set_rel_width * Set the estimated output width of a base relation. * + * The estimated output width is the sum of the per-attribute width estimates + * for the actually-referenced columns, plus any PHVs or other expressions + * that have to be calculated at this relation. This is the amount of data + * we'd need to pass upwards in case of a sort, hash, etc. + * * NB: this works best on plain relations because it prefers to look at - * real Vars. It will fail to make use of pg_statistic info when applied - * to a subquery relation, even if the subquery outputs are simple vars - * that we could have gotten info for. Is it worth trying to be smarter - * about subqueries? + * real Vars. For subqueries, set_subquery_size_estimates will already have + * copied up whatever per-column estimates were made within the subquery, + * and for other types of rels there isn't much we can do anyway. We fall + * back on (fairly stupid) datatype-based width estimates if we can't get + * any better number. * * The per-attribute width estimates are cached for possible re-use while * building join relations. @@ -3173,6 +3431,7 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) { Oid reloid = planner_rt_fetch(rel->relid, root)->relid; int32 tuple_width = 0; + bool have_wholerow_var = false; ListCell *lc; foreach(lc, rel->reltargetlist) @@ -3192,8 +3451,18 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) ndx = var->varattno - rel->min_attr; /* - * The width probably hasn't been cached yet, but may as well - * check + * If it's a whole-row Var, we'll deal with it below after we have + * already cached as many attr widths as possible. + */ + if (var->varattno == 0) + { + have_wholerow_var = true; + continue; + } + + /* + * The width may have been cached already (especially if it's a + * subquery), so don't duplicate effort. */ if (rel->attr_widths[ndx] > 0) { @@ -3202,7 +3471,7 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) } /* Try to get column width from statistics */ - if (reloid != InvalidOid) + if (reloid != InvalidOid && var->varattno > 0) { item_width = get_attavgwidth(reloid, var->varattno); if (item_width > 0) @@ -3243,6 +3512,39 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) tuple_width += item_width; } } + + /* + * If we have a whole-row reference, estimate its width as the sum of + * per-column widths plus sizeof(HeapTupleHeaderData). + */ + if (have_wholerow_var) + { + int32 wholerow_width = sizeof(HeapTupleHeaderData); + + if (reloid != InvalidOid) + { + /* Real relation, so estimate true tuple width */ + wholerow_width += get_relation_data_width(reloid, + rel->attr_widths - rel->min_attr); + } + else + { + /* Do what we can with info for a phony rel */ + AttrNumber i; + + for (i = 1; i <= rel->max_attr; i++) + wholerow_width += rel->attr_widths[i - rel->min_attr]; + } + + rel->attr_widths[0 - rel->min_attr] = wholerow_width; + + /* + * Include the whole-row Var as part of the output tuple. Yes, that + * really is what happens at runtime. + */ + tuple_width += wholerow_width; + } + Assert(tuple_width >= 0); rel->width = tuple_width; } diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 75219d0f33..a365beecd8 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -6,17 +6,19 @@ * See src/backend/optimizer/README for discussion of EquivalenceClasses. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/equivclass.c,v 1.23 2010/02/26 02:00:44 momjian Exp $ + * src/backend/optimizer/path/equivclass.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/skey.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -78,6 +80,10 @@ static bool reconsider_full_join_clause(PlannerInfo *root, * join. (This is the reason why we need a failure return. It's more * convenient to check this case here than at the call sites...) * + * On success return, we have also initialized the clause's left_ec/right_ec + * fields to point to the EquivalenceClass representing it. This saves lookup + * effort later. + * * Note: constructing merged EquivalenceClasses is a standard UNION-FIND * problem, for which there exist better data structures than simple lists. * If this code ever proves to be a bottleneck then it could be sped up --- @@ -93,6 +99,7 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, { Expr *clause = restrictinfo->clause; Oid opno, + collation, item1_type, item2_type; Expr *item1; @@ -106,15 +113,31 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, *em2; ListCell *lc1; + /* Should not already be marked as having generated an eclass */ + Assert(restrictinfo->left_ec == NULL); + Assert(restrictinfo->right_ec == NULL); + /* Extract info from given clause */ Assert(is_opclause(clause)); opno = ((OpExpr *) clause)->opno; + collation = ((OpExpr *) clause)->inputcollid; item1 = (Expr *) get_leftop(clause); item2 = (Expr *) get_rightop(clause); item1_relids = restrictinfo->left_relids; item2_relids = restrictinfo->right_relids; /* + * Ensure both input expressions expose the desired collation (their types + * should be OK already); see comments for canonicalize_ec_expression. + */ + item1 = canonicalize_ec_expression(item1, + exprType((Node *) item1), + collation); + item2 = canonicalize_ec_expression(item2, + exprType((Node *) item2), + collation); + + /* * Reject clauses of the form X=X. These are not as redundant as they * might seem at first glance: assuming the operator is strict, this is * really an expensive way to write X IS NOT NULL. So we must not risk @@ -181,6 +204,13 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, continue; /* + * The collation has to match; check this first since it's cheaper + * than the opfamily comparison. + */ + if (collation != cur_ec->ec_collation) + continue; + + /* * A "match" requires matching sets of btree opfamilies. Use of * equal() for this test has implications discussed in the comments * for get_mergejoin_opfamilies(). @@ -236,8 +266,10 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, { ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_below_outer_join |= below_outer_join; + /* mark the RI as associated with this eclass */ + restrictinfo->left_ec = ec1; + restrictinfo->right_ec = ec1; /* mark the RI as usable with this pair of EMs */ - /* NB: can't set left_ec/right_ec until merging is finished */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; return true; @@ -266,6 +298,9 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, ec2->ec_relids = NULL; ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_below_outer_join |= below_outer_join; + /* mark the RI as associated with this eclass */ + restrictinfo->left_ec = ec1; + restrictinfo->right_ec = ec1; /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; @@ -276,6 +311,9 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, em2 = add_eq_member(ec1, item2, item2_relids, false, item2_type); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_below_outer_join |= below_outer_join; + /* mark the RI as associated with this eclass */ + restrictinfo->left_ec = ec1; + restrictinfo->right_ec = ec1; /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; @@ -286,6 +324,9 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, em1 = add_eq_member(ec2, item1, item1_relids, false, item1_type); ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo); ec2->ec_below_outer_join |= below_outer_join; + /* mark the RI as associated with this eclass */ + restrictinfo->left_ec = ec2; + restrictinfo->right_ec = ec2; /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; @@ -296,6 +337,7 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, EquivalenceClass *ec = makeNode(EquivalenceClass); ec->ec_opfamilies = opfamilies; + ec->ec_collation = collation; ec->ec_members = NIL; ec->ec_sources = list_make1(restrictinfo); ec->ec_derives = NIL; @@ -311,6 +353,9 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, root->eq_classes = lappend(root->eq_classes, ec); + /* mark the RI as associated with this eclass */ + restrictinfo->left_ec = ec; + restrictinfo->right_ec = ec; /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; @@ -320,6 +365,84 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, } /* + * canonicalize_ec_expression + * + * This function ensures that the expression exposes the expected type and + * collation, so that it will be equal() to other equivalence-class expressions + * that it ought to be equal() to. + * + * The rule for datatypes is that the exposed type should match what it would + * be for an input to an operator of the EC's opfamilies; which is usually + * the declared input type of the operator, but in the case of polymorphic + * operators no relabeling is wanted (compare the behavior of parse_coerce.c). + * Expressions coming in from quals will generally have the right type + * already, but expressions coming from indexkeys may not (because they are + * represented without any explicit relabel in pg_index), and the same problem + * occurs for sort expressions (because the parser is likewise cavalier about + * putting relabels on them). Such cases will be binary-compatible with the + * real operators, so adding a RelabelType is sufficient. + * + * Also, the expression's exposed collation must match the EC's collation. + * This is important because in comparisons like "foo < bar COLLATE baz", + * only one of the expressions has the correct exposed collation as we receive + * it from the parser. Forcing both of them to have it ensures that all + * variant spellings of such a construct behave the same. Again, we can + * stick on a RelabelType to force the right exposed collation. (It might + * work to not label the collation at all in EC members, but this is risky + * since some parts of the system expect exprCollation() to deliver the + * right answer for a sort key.) + * + * Note this code assumes that the expression has already been through + * eval_const_expressions, so there are no CollateExprs and no redundant + * RelabelTypes. + */ +Expr * +canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) +{ + Oid expr_type = exprType((Node *) expr); + + /* + * For a polymorphic-input-type opclass, just keep the same exposed type. + */ + if (IsPolymorphicType(req_type)) + req_type = expr_type; + + /* + * No work if the expression exposes the right type/collation already. + */ + if (expr_type != req_type || + exprCollation((Node *) expr) != req_collation) + { + /* + * Strip any existing RelabelType, then add a new one if needed. This + * is to preserve the invariant of no redundant RelabelTypes. + * + * If we have to change the exposed type of the stripped expression, + * set typmod to -1 (since the new type may not have the same typmod + * interpretation). If we only have to change collation, preserve the + * exposed typmod. + */ + while (expr && IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + if (exprType((Node *) expr) != req_type) + expr = (Expr *) makeRelabelType(expr, + req_type, + -1, + req_collation, + COERCE_DONTCARE); + else if (exprCollation((Node *) expr) != req_collation) + expr = (Expr *) makeRelabelType(expr, + req_type, + exprTypmod((Node *) expr), + req_collation, + COERCE_DONTCARE); + } + + return expr; +} + +/* * add_eq_member - build a new EquivalenceMember and add it to an EC */ static EquivalenceMember * @@ -361,16 +484,20 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, /* * get_eclass_for_sort_expr - * Given an expression and opfamily info, find an existing equivalence - * class it is a member of; if none, build a new single-member - * EquivalenceClass for it. + * Given an expression and opfamily/collation info, find an existing + * equivalence class it is a member of; if none, optionally build a new + * single-member EquivalenceClass for it. * * sortref is the SortGroupRef of the originating SortGroupClause, if any, * or zero if not. (It should never be zero if the expression is volatile!) * + * If create_it is TRUE, we'll build a new EquivalenceClass when there is no + * match. If create_it is FALSE, we just return NULL when no match. + * * This can be used safely both before and after EquivalenceClass merging; * since it never causes merging it does not invalidate any existing ECs - * or PathKeys. + * or PathKeys. However, ECs added after path generation has begun are + * of limited usefulness, so usually it's best to create them beforehand. * * Note: opfamilies must be chosen consistently with the way * process_equivalence() would do; that is, generated from a mergejoinable @@ -380,9 +507,11 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, EquivalenceClass * get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, - Oid expr_datatype, List *opfamilies, - Index sortref) + Oid opcintype, + Oid collation, + Index sortref, + bool create_it) { EquivalenceClass *newec; EquivalenceMember *newem; @@ -390,6 +519,11 @@ get_eclass_for_sort_expr(PlannerInfo *root, MemoryContext oldcontext; /* + * Ensure the expression exposes the correct type and collation. + */ + expr = canonicalize_ec_expression(expr, opcintype, collation); + + /* * Scan through the existing EquivalenceClasses for a match */ foreach(lc1, root->eq_classes) @@ -405,6 +539,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, (sortref == 0 || sortref != cur_ec->ec_sortref)) continue; + if (collation != cur_ec->ec_collation) + continue; if (!equal(opfamilies, cur_ec->ec_opfamilies)) continue; @@ -420,22 +556,26 @@ get_eclass_for_sort_expr(PlannerInfo *root, cur_em->em_is_const) continue; - if (expr_datatype == cur_em->em_datatype && + if (opcintype == cur_em->em_datatype && equal(expr, cur_em->em_expr)) return cur_ec; /* Match! */ } } + /* No match; does caller want a NULL result? */ + if (!create_it) + return NULL; + /* - * No match, so build a new single-member EC + * OK, build a new single-member EC * - * Here, we must be sure that we construct the EC in the right context. We - * can assume, however, that the passed expr is long-lived. + * Here, we must be sure that we construct the EC in the right context. */ oldcontext = MemoryContextSwitchTo(root->planner_cxt); newec = makeNode(EquivalenceClass); newec->ec_opfamilies = list_copy(opfamilies); + newec->ec_collation = collation; newec->ec_members = NIL; newec->ec_sources = NIL; newec->ec_derives = NIL; @@ -450,8 +590,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (newec->ec_has_volatile && sortref == 0) /* should not happen */ elog(ERROR, "volatile EquivalenceClass has no sortref"); - newem = add_eq_member(newec, expr, pull_varnos((Node *) expr), - false, expr_datatype); + newem = add_eq_member(newec, copyObject(expr), pull_varnos((Node *) expr), + false, opcintype); /* * add_eq_member doesn't check for volatile functions, set-returning @@ -629,7 +769,7 @@ generate_base_implied_equalities_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, + process_implied_equality(root, eq_op, ec->ec_collation, cur_em->em_expr, const_em->em_expr, ec->ec_relids, ec->ec_below_outer_join, @@ -684,7 +824,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, + process_implied_equality(root, eq_op, ec->ec_collation, prev_em->em_expr, cur_em->em_expr, ec->ec_relids, ec->ec_below_outer_join, @@ -858,8 +998,8 @@ generate_join_implied_equalities_normal(PlannerInfo *root, * combination for which an opfamily member operator exists. If we have * choices, we prefer simple Var members (possibly with RelabelType) since * these are (a) cheapest to compute at runtime and (b) most likely to - * have useful statistics. Also, if enable_hashjoin is on, we prefer - * operators that are also hashjoinable. + * have useful statistics. Also, prefer operators that are also + * hashjoinable. */ if (outer_members && inner_members) { @@ -894,7 +1034,8 @@ generate_join_implied_equalities_normal(PlannerInfo *root, (IsA(inner_em->em_expr, RelabelType) && IsA(((RelabelType *) inner_em->em_expr)->arg, Var))) score++; - if (!enable_hashjoin || op_hashjoinable(eq_op)) + if (op_hashjoinable(eq_op, + exprType((Node *) outer_em->em_expr))) score++; if (score > best_score) { @@ -1085,6 +1226,7 @@ create_join_clause(PlannerInfo *root, oldcontext = MemoryContextSwitchTo(root->planner_cxt); rinfo = build_implied_join_equality(opno, + ec->ec_collation, leftem->em_expr, rightem->em_expr, bms_union(leftem->em_relids, @@ -1094,8 +1236,8 @@ create_join_clause(PlannerInfo *root, rinfo->parent_ec = parent_ec; /* - * We can set these now, rather than letting them be looked up later, - * since this is only used after EC merging is complete. + * We know the correct values for left_ec/right_ec, ie this particular EC, + * so we can just set them directly instead of forcing another lookup. */ rinfo->left_ec = ec; rinfo->right_ec = ec; @@ -1306,6 +1448,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, Expr *outervar, *innervar; Oid opno, + collation, left_type, right_type, inner_datatype; @@ -1314,6 +1457,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; + collation = ((OpExpr *) rinfo->clause)->inputcollid; /* If clause is outerjoin_delayed, operator must be strict */ if (rinfo->outerjoin_delayed && !op_strict(opno)) @@ -1349,7 +1493,9 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, /* Never match to a volatile EC */ if (cur_ec->ec_has_volatile) continue; - /* It has to match the outer-join clause as to opfamilies, too */ + /* It has to match the outer-join clause as to semantics, too */ + if (collation != cur_ec->ec_collation) + continue; if (!equal(rinfo->mergeopfamilies, cur_ec->ec_opfamilies)) continue; /* Does it contain a match to outervar? */ @@ -1387,6 +1533,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, if (!OidIsValid(eq_op)) continue; /* can't generate equality */ newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, innervar, cur_em->em_expr, inner_relids); @@ -1419,6 +1566,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) Expr *leftvar; Expr *rightvar; Oid opno, + collation, left_type, right_type; Relids left_relids, @@ -1432,6 +1580,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) /* Extract needed info from the clause */ Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; + collation = ((OpExpr *) rinfo->clause)->inputcollid; op_input_types(opno, &left_type, &right_type); leftvar = (Expr *) get_leftop(rinfo->clause); rightvar = (Expr *) get_rightop(rinfo->clause); @@ -1453,7 +1602,9 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) /* Never match to a volatile EC */ if (cur_ec->ec_has_volatile) continue; - /* It has to match the outer-join clause as to opfamilies, too */ + /* It has to match the outer-join clause as to semantics, too */ + if (collation != cur_ec->ec_collation) + continue; if (!equal(rinfo->mergeopfamilies, cur_ec->ec_opfamilies)) continue; @@ -1516,6 +1667,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) if (OidIsValid(eq_op)) { newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, leftvar, cur_em->em_expr, left_relids); @@ -1528,6 +1680,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) if (OidIsValid(eq_op)) { newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, rightvar, cur_em->em_expr, right_relids); @@ -1611,8 +1764,8 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) * Search for EC members that reference (only) the parent_rel, and * add transformed members referencing the child_rel. * - * We only need to do this for ECs that could generate join conditions, - * since the child members are only used for creating inner-indexscan paths. + * Note that this function won't be called at all unless we have at least some + * reason to believe that the EC members it generates will be useful. * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. @@ -1631,10 +1784,14 @@ add_child_rel_equivalences(PlannerInfo *root, ListCell *lc2; /* - * Won't generate joinclauses if const or single-member (the latter - * test covers the volatile case too) + * If this EC contains a constant, then it's not useful for sorting or + * driving an inner index-scan, so we skip generating child EMs. + * + * If this EC contains a volatile expression, then generating child + * EMs would be downright dangerous. We rely on a volatile EC having + * only one EM. */ - if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1) + if (cur_ec->ec_has_const || cur_ec->ec_has_volatile) continue; /* No point in searching if parent rel not mentioned in eclass */ diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 2c97bea3fa..1cace6d596 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -4,12 +4,12 @@ * Routines to determine which indexes are usable for scanning a * given relation, and create Paths accordingly. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.246 2010/02/26 02:00:44 momjian Exp $ + * src/backend/optimizer/path/indxpath.c * *------------------------------------------------------------------------- */ @@ -19,6 +19,7 @@ #include "access/skey.h" #include "catalog/pg_am.h" +#include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" @@ -37,14 +38,16 @@ #include "utils/selfuncs.h" -/* - * DoneMatchingIndexKeys() - MACRO - */ -#define DoneMatchingIndexKeys(families) (families[0] == InvalidOid) - #define IsBooleanOpfamily(opfamily) \ ((opfamily) == BOOL_BTREE_FAM_OID || (opfamily) == BOOL_HASH_FAM_OID) +/* Whether to use ScalarArrayOpExpr to build index qualifications */ +typedef enum +{ + SAOP_FORBID, /* Do not use ScalarArrayOpExpr */ + SAOP_ALLOW, /* OK to use ScalarArrayOpExpr */ + SAOP_REQUIRE /* Require ScalarArrayOpExpr */ +} SaOpControl; /* Whether we are looking for plain indexscan, bitmap scan, or either */ typedef enum @@ -82,8 +85,13 @@ static PathClauseUsage *classify_index_clause_usage(Path *path, List **clauselist); static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds); static int find_list_position(Node *node, List **nodelist); +static List *group_clauses_by_indexkey(IndexOptInfo *index, + List *clauses, List *outer_clauses, + Relids outer_relids, + SaOpControl saop_control, + bool *found_clause); static bool match_clause_to_indexcol(IndexOptInfo *index, - int indexcol, Oid opfamily, + int indexcol, RestrictInfo *rinfo, Relids outer_relids, SaOpControl saop_control); @@ -92,8 +100,12 @@ static bool is_indexable_operator(Oid expr_op, Oid opfamily, static bool match_rowcompare_to_indexcol(IndexOptInfo *index, int indexcol, Oid opfamily, + Oid idxcollation, RowCompareExpr *clause, Relids outer_relids); +static List *match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys); +static Expr *match_clause_to_ordering_op(IndexOptInfo *index, + int indexcol, Expr *clause, Oid pk_opfamily); static Relids indexable_outerrelids(PlannerInfo *root, RelOptInfo *rel); static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids); @@ -101,15 +113,17 @@ static List *find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel, Relids outer_relids, bool isouterjoin); static bool match_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); -static bool match_special_index_operator(Expr *clause, Oid opfamily, +static bool match_special_index_operator(Expr *clause, + Oid opfamily, Oid idxcollation, bool indexkey_on_left); static Expr *expand_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); -static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily); +static List *expand_indexqual_opclause(RestrictInfo *rinfo, + Oid opfamily, Oid idxcollation); static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo, IndexOptInfo *index, int indexcol); -static List *prefix_quals(Node *leftop, Oid opfamily, +static List *prefix_quals(Node *leftop, Oid opfamily, Oid collation, Const *prefix, Pattern_Prefix_Status pstatus); static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, Datum rightop); @@ -198,8 +212,8 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) add_path(rel, (Path *) ipath); if (ipath->indexinfo->amhasgetbitmap && - ipath->indexselectivity < 1.0 && - !ScanDirectionIsBackward(ipath->indexscandir)) + (ipath->path.pathkeys == NIL || + ipath->indexselectivity < 1.0)) bitindexpaths = lappend(bitindexpaths, ipath); } @@ -291,6 +305,7 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); IndexPath *ipath; List *restrictclauses; + List *orderbyclauses; List *index_pathkeys; List *useful_pathkeys; bool useful_predicate; @@ -385,7 +400,7 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, * how many of them are actually useful for this query. This is not * relevant unless we are at top level. */ - index_is_ordered = OidIsValid(index->fwdsortop[0]); + index_is_ordered = (index->sortopfamily != NULL); if (index_is_ordered && possibly_useful_pathkeys && istoplevel && outer_rel == NULL) { @@ -393,9 +408,24 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, ForwardScanDirection); useful_pathkeys = truncate_useless_pathkeys(root, rel, index_pathkeys); + orderbyclauses = NIL; + } + else if (index->amcanorderbyop && possibly_useful_pathkeys && + istoplevel && outer_rel == NULL && scantype != ST_BITMAPSCAN) + { + /* see if we can generate ordering operators for query_pathkeys */ + orderbyclauses = match_index_to_pathkeys(index, + root->query_pathkeys); + if (orderbyclauses) + useful_pathkeys = root->query_pathkeys; + else + useful_pathkeys = NIL; } else + { useful_pathkeys = NIL; + orderbyclauses = NIL; + } /* * 3. Generate an indexscan path if there are relevant restriction @@ -407,6 +437,7 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, { ipath = create_index_path(root, index, restrictclauses, + orderbyclauses, useful_pathkeys, index_is_ordered ? ForwardScanDirection : @@ -430,6 +461,7 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, { ipath = create_index_path(root, index, restrictclauses, + NIL, useful_pathkeys, BackwardScanDirection, outer_rel); @@ -1043,7 +1075,7 @@ find_list_position(Node *node, List **nodelist) * from multiple places. Defend against redundant outputs by using * list_append_unique_ptr (pointer equality should be good enough). */ -List * +static List * group_clauses_by_indexkey(IndexOptInfo *index, List *clauses, List *outer_clauses, Relids outer_relids, @@ -1052,17 +1084,15 @@ group_clauses_by_indexkey(IndexOptInfo *index, { List *clausegroup_list = NIL; bool found_outer_clause = false; - int indexcol = 0; - Oid *families = index->opfamily; + int indexcol; *found_clause = false; /* default result */ if (clauses == NIL && outer_clauses == NIL) return NIL; /* cannot succeed */ - do + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) { - Oid curFamily = families[0]; List *clausegroup = NIL; ListCell *l; @@ -1074,7 +1104,6 @@ group_clauses_by_indexkey(IndexOptInfo *index, Assert(IsA(rinfo, RestrictInfo)); if (match_clause_to_indexcol(index, indexcol, - curFamily, rinfo, outer_relids, saop_control)) @@ -1094,7 +1123,6 @@ group_clauses_by_indexkey(IndexOptInfo *index, Assert(IsA(rinfo, RestrictInfo)); if (match_clause_to_indexcol(index, indexcol, - curFamily, rinfo, outer_relids, saop_control)) @@ -1111,11 +1139,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, return NIL; clausegroup_list = lappend(clausegroup_list, clausegroup); - - indexcol++; - families++; - - } while (!DoneMatchingIndexKeys(families)); + } if (!*found_clause && !found_outer_clause) return NIL; /* no indexable clauses anywhere */ @@ -1134,7 +1158,9 @@ group_clauses_by_indexkey(IndexOptInfo *index, * and * (2) must contain an operator which is in the same family as the index * operator for this column, or is a "special" operator as recognized - * by match_special_index_operator(). + * by match_special_index_operator(); + * and + * (3) must match the collation of the index, if collation is relevant. * * Our definition of "const" is pretty liberal: we allow Vars belonging * to the caller-specified outer_relids relations (which had better not @@ -1173,8 +1199,8 @@ group_clauses_by_indexkey(IndexOptInfo *index, * * 'index' is the index of interest. * 'indexcol' is a column number of 'index' (counting from 0). - * 'opfamily' is the corresponding operator family. * 'rinfo' is the clause to be tested (as a RestrictInfo node). + * 'outer_relids' lists rels whose Vars can be considered pseudoconstant. * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used. * * Returns true if the clause can be used with this index key. @@ -1185,17 +1211,19 @@ group_clauses_by_indexkey(IndexOptInfo *index, static bool match_clause_to_indexcol(IndexOptInfo *index, int indexcol, - Oid opfamily, RestrictInfo *rinfo, Relids outer_relids, SaOpControl saop_control) { Expr *clause = rinfo->clause; + Oid opfamily = index->opfamily[indexcol]; + Oid idxcollation = index->indexcollations[indexcol]; Node *leftop, *rightop; Relids left_relids; Relids right_relids; Oid expr_op; + Oid expr_coll; bool plain_op; /* @@ -1229,6 +1257,7 @@ match_clause_to_indexcol(IndexOptInfo *index, left_relids = rinfo->left_relids; right_relids = rinfo->right_relids; expr_op = ((OpExpr *) clause)->opno; + expr_coll = ((OpExpr *) clause)->inputcollid; plain_op = true; } else if (saop_control != SAOP_FORBID && @@ -1244,11 +1273,13 @@ match_clause_to_indexcol(IndexOptInfo *index, left_relids = NULL; /* not actually needed */ right_relids = pull_varnos(rightop); expr_op = saop->opno; + expr_coll = saop->inputcollid; plain_op = false; } else if (clause && IsA(clause, RowCompareExpr)) { - return match_rowcompare_to_indexcol(index, indexcol, opfamily, + return match_rowcompare_to_indexcol(index, indexcol, + opfamily, idxcollation, (RowCompareExpr *) clause, outer_relids); } @@ -1272,7 +1303,8 @@ match_clause_to_indexcol(IndexOptInfo *index, bms_is_subset(right_relids, outer_relids) && !contain_volatile_functions(rightop)) { - if (is_indexable_operator(expr_op, opfamily, true)) + if (idxcollation == expr_coll && + is_indexable_operator(expr_op, opfamily, true)) return true; /* @@ -1280,7 +1312,7 @@ match_clause_to_indexcol(IndexOptInfo *index, * is a "special" indexable operator. */ if (plain_op && - match_special_index_operator(clause, opfamily, true)) + match_special_index_operator(clause, opfamily, idxcollation, true)) return true; return false; } @@ -1290,14 +1322,15 @@ match_clause_to_indexcol(IndexOptInfo *index, bms_is_subset(left_relids, outer_relids) && !contain_volatile_functions(leftop)) { - if (is_indexable_operator(expr_op, opfamily, false)) + if (idxcollation == expr_coll && + is_indexable_operator(expr_op, opfamily, false)) return true; /* * If we didn't find a member of the index's opfamily, see whether it * is a "special" indexable operator. */ - if (match_special_index_operator(clause, opfamily, false)) + if (match_special_index_operator(clause, opfamily, idxcollation, false)) return true; return false; } @@ -1337,12 +1370,14 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index, int indexcol, Oid opfamily, + Oid idxcollation, RowCompareExpr *clause, Relids outer_relids) { Node *leftop, *rightop; Oid expr_op; + Oid expr_coll; /* Forget it if we're not dealing with a btree index */ if (index->relam != BTREE_AM_OID) @@ -1361,6 +1396,11 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, leftop = (Node *) linitial(clause->largs); rightop = (Node *) linitial(clause->rargs); expr_op = linitial_oid(clause->opnos); + expr_coll = linitial_oid(clause->inputcollids); + + /* Collations must match */ + if (expr_coll != idxcollation) + return false; /* * These syntactic tests are the same as in match_clause_to_indexcol() @@ -1398,6 +1438,194 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, /**************************************************************************** + * ---- ROUTINES TO CHECK ORDERING OPERATORS ---- + ****************************************************************************/ + +/* + * match_index_to_pathkeys + * Test whether an index can produce output ordered according to the + * given pathkeys using "ordering operators". + * + * If it can, return a list of suitable ORDER BY expressions, each of the form + * "indexedcol operator pseudoconstant". If not, return NIL. + */ +static List * +match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) +{ + List *orderbyexprs = NIL; + ListCell *lc1; + + /* Only indexes with the amcanorderbyop property are interesting here */ + if (!index->amcanorderbyop) + return NIL; + + foreach(lc1, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(lc1); + bool found = false; + ListCell *lc2; + + /* + * Note: for any failure to match, we just return NIL immediately. + * There is no value in matching just some of the pathkeys. + */ + + /* Pathkey must request default sort order for the target opfamily */ + if (pathkey->pk_strategy != BTLessStrategyNumber || + pathkey->pk_nulls_first) + return NIL; + + /* If eclass is volatile, no hope of using an indexscan */ + if (pathkey->pk_eclass->ec_has_volatile) + return NIL; + + /* Try to match eclass member expression(s) to index */ + foreach(lc2, pathkey->pk_eclass->ec_members) + { + EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2); + int indexcol; + + /* No possibility of match if it references other relations */ + if (!bms_equal(member->em_relids, index->rel->relids)) + continue; + + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) + { + Expr *expr; + + expr = match_clause_to_ordering_op(index, + indexcol, + member->em_expr, + pathkey->pk_opfamily); + if (expr) + { + orderbyexprs = lappend(orderbyexprs, expr); + found = true; + break; + } + } + + if (found) /* don't want to look at remaining members */ + break; + } + + if (!found) /* fail if no match for this pathkey */ + return NIL; + } + + return orderbyexprs; /* success! */ +} + +/* + * match_clause_to_ordering_op + * Determines whether an ordering operator expression matches an + * index column. + * + * This is similar to, but simpler than, match_clause_to_indexcol. + * We only care about simple OpExpr cases. The input is a bare + * expression that is being ordered by, which must be of the form + * (indexkey op const) or (const op indexkey) where op is an ordering + * operator for the column's opfamily. + * + * 'index' is the index of interest. + * 'indexcol' is a column number of 'index' (counting from 0). + * 'clause' is the ordering expression to be tested. + * 'pk_opfamily' is the btree opfamily describing the required sort order. + * + * Note that we currently do not consider the collation of the ordering + * operator's result. In practical cases the result type will be numeric + * and thus have no collation, and it's not very clear what to match to + * if it did have a collation. The index's collation should match the + * ordering operator's input collation, not its result. + * + * If successful, return 'clause' as-is if the indexkey is on the left, + * otherwise a commuted copy of 'clause'. If no match, return NULL. + */ +static Expr * +match_clause_to_ordering_op(IndexOptInfo *index, + int indexcol, + Expr *clause, + Oid pk_opfamily) +{ + Oid opfamily = index->opfamily[indexcol]; + Oid idxcollation = index->indexcollations[indexcol]; + Node *leftop, + *rightop; + Oid expr_op; + Oid expr_coll; + Oid sortfamily; + bool commuted; + + /* + * Clause must be a binary opclause. + */ + if (!is_opclause(clause)) + return NULL; + leftop = get_leftop(clause); + rightop = get_rightop(clause); + if (!leftop || !rightop) + return NULL; + expr_op = ((OpExpr *) clause)->opno; + expr_coll = ((OpExpr *) clause)->inputcollid; + + /* + * We can forget the whole thing right away if wrong collation. + */ + if (expr_coll != idxcollation) + return NULL; + + /* + * Check for clauses of the form: (indexkey operator constant) or + * (constant operator indexkey). + */ + if (match_index_to_operand(leftop, indexcol, index) && + !contain_var_clause(rightop) && + !contain_volatile_functions(rightop)) + { + commuted = false; + } + else if (match_index_to_operand(rightop, indexcol, index) && + !contain_var_clause(leftop) && + !contain_volatile_functions(leftop)) + { + /* Might match, but we need a commuted operator */ + expr_op = get_commutator(expr_op); + if (expr_op == InvalidOid) + return NULL; + commuted = true; + } + else + return NULL; + + /* + * Is the (commuted) operator an ordering operator for the opfamily? And + * if so, does it yield the right sorting semantics? + */ + sortfamily = get_op_opfamily_sortfamily(expr_op, opfamily); + if (sortfamily != pk_opfamily) + return NULL; + + /* We have a match. Return clause or a commuted version thereof. */ + if (commuted) + { + OpExpr *newclause = makeNode(OpExpr); + + /* flat-copy all the fields of clause */ + memcpy(newclause, clause, sizeof(OpExpr)); + + /* commute it */ + newclause->opno = expr_op; + newclause->opfuncid = InvalidOid; + newclause->args = list_make2(rightop, leftop); + + clause = (Expr *) newclause; + } + + return clause; +} + + +/**************************************************************************** * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- ****************************************************************************/ @@ -1581,24 +1809,17 @@ matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids) foreach(l, rel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(l); - int indexcol = 0; - Oid *families = index->opfamily; + int indexcol; - do + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) { - Oid curFamily = families[0]; - if (match_clause_to_indexcol(index, indexcol, - curFamily, rinfo, outer_relids, SAOP_ALLOW)) return true; - - indexcol++; - families++; - } while (!DoneMatchingIndexKeys(families)); + } } return false; @@ -1620,12 +1841,12 @@ eclass_matches_any_index(EquivalenceClass *ec, EquivalenceMember *em, foreach(l, rel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(l); - int indexcol = 0; - Oid *families = index->opfamily; + int indexcol; - do + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) { - Oid curFamily = families[0]; + Oid curFamily = index->opfamily[indexcol]; + Oid curCollation = index->indexcollations[indexcol]; /* * If it's a btree index, we can reject it if its opfamily isn't @@ -1636,15 +1857,15 @@ eclass_matches_any_index(EquivalenceClass *ec, EquivalenceMember *em, * mean we return "true" for a useless index, but that will just * cause some wasted planner cycles; it's better than ignoring * useful indexes. + * + * We insist on collation match for all index types, though. */ if ((index->relam != BTREE_AM_OID || list_member_oid(ec->ec_opfamilies, curFamily)) && + ec->ec_collation == curCollation && match_index_to_operand((Node *) em->em_expr, indexcol, index)) return true; - - indexcol++; - families++; - } while (!DoneMatchingIndexKeys(families)); + } } return false; @@ -1976,6 +2197,12 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, if (!list_member_oid(rinfo->mergeopfamilies, ind->opfamily[c])) continue; + /* + * XXX at some point we may need to check collations here too. + * For the moment we assume all collations reduce to the same + * notion of equality. + */ + /* OK, see if the condition operand matches the index key */ if (rinfo->outer_is_left) rexpr = get_rightop(rinfo->clause); @@ -2036,6 +2263,9 @@ flatten_clausegroups_list(List *clausegroups) * operand: the nodetree to be compared to the index * indexcol: the column number of the index (counting from 0) * index: the index of interest + * + * Note that we aren't interested in collations here; the caller must check + * for a collation match, if it's dealing with an operator where that matters. */ bool match_index_to_operand(Node *operand, @@ -2210,12 +2440,13 @@ match_boolean_index_clause(Node *clause, * Return 'true' if we can do something with it anyway. */ static bool -match_special_index_operator(Expr *clause, Oid opfamily, +match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation, bool indexkey_on_left) { bool isIndexable = false; Node *rightop; Oid expr_op; + Oid expr_coll; Const *patt; Const *prefix = NULL; Const *rest = NULL; @@ -2232,6 +2463,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, /* we know these will succeed */ rightop = get_rightop(clause); expr_op = ((OpExpr *) clause)->opno; + expr_coll = ((OpExpr *) clause)->inputcollid; /* again, required for all current special ops: */ if (!IsA(rightop, Const) || @@ -2245,13 +2477,13 @@ match_special_index_operator(Expr *clause, Oid opfamily, case OID_BPCHAR_LIKE_OP: case OID_NAME_LIKE_OP: /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, &prefix, &rest); isIndexable = (pstatus != Pattern_Prefix_None); break; case OID_BYTEA_LIKE_OP: - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, &prefix, &rest); isIndexable = (pstatus != Pattern_Prefix_None); break; @@ -2260,7 +2492,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, case OID_BPCHAR_ICLIKE_OP: case OID_NAME_ICLIKE_OP: /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, expr_coll, &prefix, &rest); isIndexable = (pstatus != Pattern_Prefix_None); break; @@ -2269,7 +2501,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, case OID_BPCHAR_REGEXEQ_OP: case OID_NAME_REGEXEQ_OP: /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, expr_coll, &prefix, &rest); isIndexable = (pstatus != Pattern_Prefix_None); break; @@ -2278,7 +2510,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, case OID_BPCHAR_ICREGEXEQ_OP: case OID_NAME_ICREGEXEQ_OP: /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, expr_coll, &prefix, &rest); isIndexable = (pstatus != Pattern_Prefix_None); break; @@ -2314,7 +2546,9 @@ match_special_index_operator(Expr *clause, Oid opfamily, * * The non-pattern opclasses will not sort the way we need in most non-C * locales. We can use such an index anyway for an exact match (simple - * equality), but not for prefix-match cases. + * equality), but not for prefix-match cases. Note that here we are + * looking at the index's collation, not the expression's collation -- + * this test is *not* dependent on the LIKE/regex operator's collation. */ switch (expr_op) { @@ -2325,7 +2559,8 @@ match_special_index_operator(Expr *clause, Oid opfamily, isIndexable = (opfamily == TEXT_PATTERN_BTREE_FAM_OID) || (opfamily == TEXT_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || lc_collate_is_c())); + (pstatus == Pattern_Prefix_Exact || + lc_collate_is_c(idxcollation))); break; case OID_BPCHAR_LIKE_OP: @@ -2335,7 +2570,8 @@ match_special_index_operator(Expr *clause, Oid opfamily, isIndexable = (opfamily == BPCHAR_PATTERN_BTREE_FAM_OID) || (opfamily == BPCHAR_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || lc_collate_is_c())); + (pstatus == Pattern_Prefix_Exact || + lc_collate_is_c(idxcollation))); break; case OID_NAME_LIKE_OP: @@ -2377,22 +2613,26 @@ List * expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) { List *resultquals = NIL; - ListCell *clausegroup_item; - int indexcol = 0; - Oid *families = index->opfamily; + ListCell *lc; + int indexcol; if (clausegroups == NIL) return NIL; - clausegroup_item = list_head(clausegroups); - do + /* clausegroups must correspond to index columns */ + Assert(list_length(clausegroups) <= index->ncolumns); + + indexcol = 0; + foreach(lc, clausegroups) { - Oid curFamily = families[0]; - ListCell *l; + List *clausegroup = (List *) lfirst(lc); + Oid curFamily = index->opfamily[indexcol]; + Oid curCollation = index->indexcollations[indexcol]; + ListCell *lc2; - foreach(l, (List *) lfirst(clausegroup_item)) + foreach(lc2, clausegroup) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2); Expr *clause = rinfo->clause; /* First check for boolean cases */ @@ -2419,7 +2659,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) { resultquals = list_concat(resultquals, expand_indexqual_opclause(rinfo, - curFamily)); + curFamily, + curCollation)); } else if (IsA(clause, ScalarArrayOpExpr)) { @@ -2444,13 +2685,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) (int) nodeTag(clause)); } - clausegroup_item = lnext(clausegroup_item); - indexcol++; - families++; - } while (clausegroup_item != NULL && !DoneMatchingIndexKeys(families)); - - Assert(clausegroup_item == NULL); /* else more groups than indexkeys */ + } return resultquals; } @@ -2473,7 +2709,8 @@ expand_boolean_index_clause(Node *clause, /* convert to indexkey = TRUE */ return make_opclause(BooleanEqualOperator, BOOLOID, false, (Expr *) clause, - (Expr *) makeBoolConst(true, false)); + (Expr *) makeBoolConst(true, false), + InvalidOid, InvalidOid); } /* NOT clause? */ if (not_clause(clause)) @@ -2485,7 +2722,8 @@ expand_boolean_index_clause(Node *clause, /* convert to indexkey = FALSE */ return make_opclause(BooleanEqualOperator, BOOLOID, false, (Expr *) arg, - (Expr *) makeBoolConst(false, false)); + (Expr *) makeBoolConst(false, false), + InvalidOid, InvalidOid); } if (clause && IsA(clause, BooleanTest)) { @@ -2499,14 +2737,16 @@ expand_boolean_index_clause(Node *clause, /* convert to indexkey = TRUE */ return make_opclause(BooleanEqualOperator, BOOLOID, false, (Expr *) arg, - (Expr *) makeBoolConst(true, false)); + (Expr *) makeBoolConst(true, false), + InvalidOid, InvalidOid); } if (btest->booltesttype == IS_FALSE) { /* convert to indexkey = FALSE */ return make_opclause(BooleanEqualOperator, BOOLOID, false, (Expr *) arg, - (Expr *) makeBoolConst(false, false)); + (Expr *) makeBoolConst(false, false), + InvalidOid, InvalidOid); } /* Oops */ Assert(false); @@ -2525,7 +2765,7 @@ expand_boolean_index_clause(Node *clause, * expand special cases that were accepted by match_special_index_operator(). */ static List * -expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) +expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation) { Expr *clause = rinfo->clause; @@ -2533,6 +2773,7 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) Node *leftop = get_leftop(clause); Node *rightop = get_rightop(clause); Oid expr_op = ((OpExpr *) clause)->opno; + Oid expr_coll = ((OpExpr *) clause)->inputcollid; Const *patt = (Const *) rightop; Const *prefix = NULL; Const *rest = NULL; @@ -2554,9 +2795,9 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) case OID_BYTEA_LIKE_OP: if (!op_in_opfamily(expr_op, opfamily)) { - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); } break; @@ -2566,9 +2807,9 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) if (!op_in_opfamily(expr_op, opfamily)) { /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, expr_coll, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); } break; @@ -2578,9 +2819,9 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) if (!op_in_opfamily(expr_op, opfamily)) { /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, expr_coll, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); } break; @@ -2590,9 +2831,9 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) if (!op_in_opfamily(expr_op, opfamily)) { /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, + pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, expr_coll, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); } break; @@ -2646,6 +2887,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ListCell *largs_cell; ListCell *rargs_cell; ListCell *opnos_cell; + ListCell *collids_cell; /* We have to figure out (again) how the first col matches */ var_on_left = match_index_to_operand((Node *) linitial(clause->largs), @@ -2656,7 +2898,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, expr_op = linitial_oid(clause->opnos); if (!var_on_left) expr_op = get_commutator(expr_op); - get_op_opfamily_properties(expr_op, index->opfamily[indexcol], + get_op_opfamily_properties(expr_op, index->opfamily[indexcol], false, &op_strategy, &op_lefttype, &op_righttype); @@ -2677,6 +2919,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, largs_cell = lnext(list_head(clause->largs)); rargs_cell = lnext(list_head(clause->rargs)); opnos_cell = lnext(list_head(clause->opnos)); + collids_cell = lnext(list_head(clause->inputcollids)); while (largs_cell != NULL) { @@ -2723,8 +2966,12 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, != op_strategy) break; + /* Does collation match? */ + if (lfirst_oid(collids_cell) != index->indexcollations[i]) + break; + /* Add opfamily and datatypes to lists */ - get_op_opfamily_properties(expr_op, index->opfamily[i], + get_op_opfamily_properties(expr_op, index->opfamily[i], false, &op_strategy, &op_lefttype, &op_righttype); @@ -2737,6 +2984,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, largs_cell = lnext(largs_cell); rargs_cell = lnext(rargs_cell); opnos_cell = lnext(opnos_cell); + collids_cell = lnext(collids_cell); } /* Return clause as-is if it's all usable as index quals */ @@ -2806,6 +3054,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, rc->opnos = new_ops; rc->opfamilies = list_truncate(list_copy(clause->opfamilies), matching_cols); + rc->inputcollids = list_truncate(list_copy(clause->inputcollids), + matching_cols); rc->largs = list_truncate((List *) copyObject(clause->largs), matching_cols); rc->rargs = list_truncate((List *) copyObject(clause->rargs), @@ -2818,7 +3068,9 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false, copyObject(linitial(clause->largs)), - copyObject(linitial(clause->rargs))); + copyObject(linitial(clause->rargs)), + InvalidOid, + linitial_oid(clause->inputcollids)); return make_simple_restrictinfo(opexpr); } } @@ -2827,10 +3079,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, * Given a fixed prefix that all the "leftop" values must have, * generate suitable indexqual condition(s). opfamily is the index * operator family; we use it to deduce the appropriate comparison - * operators and operand datatypes. + * operators and operand datatypes. collation is the input collation to use. */ static List * -prefix_quals(Node *leftop, Oid opfamily, +prefix_quals(Node *leftop, Oid opfamily, Oid collation, Const *prefix_const, Pattern_Prefix_Status pstatus) { List *result; @@ -2904,7 +3156,8 @@ prefix_quals(Node *leftop, Oid opfamily, if (oproid == InvalidOid) elog(ERROR, "no = operator for opfamily %u", opfamily); expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) prefix_const); + (Expr *) leftop, (Expr *) prefix_const, + InvalidOid, collation); result = list_make1(make_simple_restrictinfo(expr)); return result; } @@ -2919,12 +3172,16 @@ prefix_quals(Node *leftop, Oid opfamily, if (oproid == InvalidOid) elog(ERROR, "no >= operator for opfamily %u", opfamily); expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) prefix_const); + (Expr *) leftop, (Expr *) prefix_const, + InvalidOid, collation); result = list_make1(make_simple_restrictinfo(expr)); /*------- * If we can create a string larger than the prefix, we can say - * "x < greaterstr". + * "x < greaterstr". NB: we rely on make_greater_string() to generate + * a guaranteed-greater string, not just a probably-greater string. + * In general this is only guaranteed in C locale, so we'd better be + * using a C-locale index collation. *------- */ oproid = get_opfamily_member(opfamily, datatype, datatype, @@ -2932,11 +3189,12 @@ prefix_quals(Node *leftop, Oid opfamily, if (oproid == InvalidOid) elog(ERROR, "no < operator for opfamily %u", opfamily); fmgr_info(get_opcode(oproid), <proc); - greaterstr = make_greater_string(prefix_const, <proc); + greaterstr = make_greater_string(prefix_const, <proc, collation); if (greaterstr) { expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) greaterstr); + (Expr *) leftop, (Expr *) greaterstr, + InvalidOid, collation); result = lappend(result, make_simple_restrictinfo(expr)); } @@ -2998,8 +3256,11 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, Datum rightop) expr = make_opclause(opr1oid, BOOLOID, false, (Expr *) leftop, - (Expr *) makeConst(datatype, -1, -1, opr1right, - false, false)); + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr1right, + false, false), + InvalidOid, InvalidOid); result = list_make1(make_simple_restrictinfo(expr)); /* create clause "key <= network_scan_last( rightop )" */ @@ -3013,8 +3274,11 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, Datum rightop) expr = make_opclause(opr2oid, BOOLOID, false, (Expr *) leftop, - (Expr *) makeConst(datatype, -1, -1, opr2right, - false, false)); + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr2right, + false, false), + InvalidOid, InvalidOid); result = lappend(result, make_simple_restrictinfo(expr)); return result; @@ -3051,8 +3315,38 @@ static Const * string_to_const(const char *str, Oid datatype) { Datum conval = string_to_datum(str, datatype); + Oid collation; + int constlen; + + /* + * We only need to support a few datatypes here, so hard-wire properties + * instead of incurring the expense of catalog lookups. + */ + switch (datatype) + { + case TEXTOID: + case VARCHAROID: + case BPCHAROID: + collation = DEFAULT_COLLATION_OID; + constlen = -1; + break; + + case NAMEOID: + collation = InvalidOid; + constlen = NAMEDATALEN; + break; + + case BYTEAOID: + collation = InvalidOid; + constlen = -1; + break; + + default: + elog(ERROR, "unexpected datatype in string_to_const: %u", + datatype); + return NULL; + } - return makeConst(datatype, -1, - ((datatype == NAMEOID) ? NAMEDATALEN : -1), + return makeConst(datatype, -1, collation, constlen, conval, false, false); } diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 1fbb2a4fe9..7d3cf425da 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -3,12 +3,12 @@ * joinpath.c * Routines to find all possible paths for processing a set of joins * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.133 2010/04/19 00:55:25 rhaas Exp $ + * src/backend/optimizer/path/joinpath.c * *------------------------------------------------------------------------- */ @@ -41,7 +41,8 @@ static List *select_mergejoin_clauses(PlannerInfo *root, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, - JoinType jointype); + JoinType jointype, + bool *mergejoin_allowed); /* @@ -77,12 +78,13 @@ add_paths_to_joinrel(PlannerInfo *root, List *restrictlist) { List *mergeclause_list = NIL; + bool mergejoin_allowed = true; /* * Find potential mergejoin clauses. We can skip this if we are not - * interested in doing a mergejoin. However, mergejoin is currently our - * only way of implementing full outer joins, so override mergejoin - * disable if it's a full join. + * interested in doing a mergejoin. However, mergejoin may be our only + * way of implementing a full outer join, so override enable_mergejoin if + * it's a full join. */ if (enable_mergejoin || jointype == JOIN_FULL) mergeclause_list = select_mergejoin_clauses(root, @@ -90,22 +92,27 @@ add_paths_to_joinrel(PlannerInfo *root, outerrel, innerrel, restrictlist, - jointype); + jointype, + &mergejoin_allowed); /* * 1. Consider mergejoin paths where both relations must be explicitly - * sorted. + * sorted. Skip this if we can't mergejoin. */ - sort_inner_and_outer(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + if (mergejoin_allowed) + sort_inner_and_outer(root, joinrel, outerrel, innerrel, + restrictlist, mergeclause_list, jointype, sjinfo); /* * 2. Consider paths where the outer relation need not be explicitly * sorted. This includes both nestloops and mergejoins where the outer - * path is already ordered. + * path is already ordered. Again, skip this if we can't mergejoin. + * (That's okay because we know that nestloop can't handle right/full + * joins at all, so it wouldn't work in the prohibited cases either.) */ - match_unsorted_outer(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + if (mergejoin_allowed) + match_unsorted_outer(root, joinrel, outerrel, innerrel, + restrictlist, mergeclause_list, jointype, sjinfo); #ifdef NOT_USED @@ -120,15 +127,17 @@ add_paths_to_joinrel(PlannerInfo *root, * those made by match_unsorted_outer when add_paths_to_joinrel() is * invoked with the two rels given in the other order. */ - match_unsorted_inner(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + if (mergejoin_allowed) + match_unsorted_inner(root, joinrel, outerrel, innerrel, + restrictlist, mergeclause_list, jointype, sjinfo); #endif /* * 4. Consider paths where both outer and inner relations must be hashed - * before being joined. + * before being joined. As above, disregard enable_hashjoin for full + * joins, because there may be no other alternative. */ - if (enable_hashjoin) + if (enable_hashjoin || jointype == JOIN_FULL) hash_inner_and_outer(root, joinrel, outerrel, innerrel, restrictlist, jointype, sjinfo); } @@ -189,38 +198,12 @@ sort_inner_and_outer(PlannerInfo *root, JoinType jointype, SpecialJoinInfo *sjinfo) { - bool useallclauses; Path *outer_path; Path *inner_path; List *all_pathkeys; ListCell *l; /* - * If we are doing a right or full join, we must use *all* the - * mergeclauses as join clauses, else we will not have a valid plan. - */ - switch (jointype) - { - case JOIN_INNER: - case JOIN_LEFT: - case JOIN_SEMI: - case JOIN_ANTI: - case JOIN_UNIQUE_OUTER: - case JOIN_UNIQUE_INNER: - useallclauses = false; - break; - case JOIN_RIGHT: - case JOIN_FULL: - useallclauses = true; - break; - default: - elog(ERROR, "unrecognized join type: %d", - (int) jointype); - useallclauses = false; /* keep compiler quiet */ - break; - } - - /* * We only consider the cheapest-total-cost input paths, since we are * assuming here that a sort is required. We will consider * cheapest-startup-cost input paths later, and only if they don't need a @@ -390,9 +373,9 @@ match_unsorted_outer(PlannerInfo *root, /* * Nestloop only supports inner, left, semi, and anti joins. Also, if we - * are doing a right or full join, we must use *all* the mergeclauses as - * join clauses, else we will not have a valid plan. (Although these two - * flags are currently inverses, keep them separate for clarity and + * are doing a right or full mergejoin, we must use *all* the mergeclauses + * as join clauses, else we will not have a valid plan. (Although these + * two flags are currently inverses, keep them separate for clarity and * possible future changes.) */ switch (jointype) @@ -574,8 +557,8 @@ match_unsorted_outer(PlannerInfo *root, * Special corner case: for "x FULL JOIN y ON true", there will be no * join clauses at all. Ordinarily we'd generate a clauseless * nestloop path, but since mergejoin is our only join type that - * supports FULL JOIN, it's necessary to generate a clauseless - * mergejoin path instead. + * supports FULL JOIN without any join clauses, it's necessary to + * generate a clauseless mergejoin path instead. */ if (mergeclauses == NIL) { @@ -781,30 +764,11 @@ hash_inner_and_outer(PlannerInfo *root, JoinType jointype, SpecialJoinInfo *sjinfo) { - bool isouterjoin; + bool isouterjoin = IS_OUTER_JOIN(jointype); List *hashclauses; ListCell *l; /* - * Hashjoin only supports inner, left, semi, and anti joins. - */ - switch (jointype) - { - case JOIN_INNER: - case JOIN_SEMI: - case JOIN_UNIQUE_OUTER: - case JOIN_UNIQUE_INNER: - isouterjoin = false; - break; - case JOIN_LEFT: - case JOIN_ANTI: - isouterjoin = true; - break; - default: - return; - } - - /* * We need to build only one hashpath for any given pair of outer and * inner relations; all of the hashable clauses will be used as keys. * @@ -963,6 +927,15 @@ best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel, * Select mergejoin clauses that are usable for a particular join. * Returns a list of RestrictInfo nodes for those clauses. * + * *mergejoin_allowed is normally set to TRUE, but it is set to FALSE if + * this is a right/full join and there are nonmergejoinable join clauses. + * The executor's mergejoin machinery cannot handle such cases, so we have + * to avoid generating a mergejoin plan. (Note that this flag does NOT + * consider whether there are actually any mergejoinable clauses. This is + * correct because in some cases we need to build a clauseless mergejoin. + * Simply returning NIL is therefore not enough to distinguish safe from + * unsafe cases.) + * * We also mark each selected RestrictInfo to show which side is currently * being considered as outer. These are transient markings that are only * good for the duration of the current add_paths_to_joinrel() call! @@ -977,7 +950,8 @@ select_mergejoin_clauses(PlannerInfo *root, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, - JoinType jointype) + JoinType jointype, + bool *mergejoin_allowed) { List *result_list = NIL; bool isouterjoin = IS_OUTER_JOIN(jointype); @@ -1041,7 +1015,7 @@ select_mergejoin_clauses(PlannerInfo *root, * mergejoin is not really all that big a deal, and so it's not clear * that improving this is important. */ - cache_mergeclause_eclasses(root, restrictinfo); + update_mergeclause_eclasses(root, restrictinfo); if (EC_MUST_BE_REDUNDANT(restrictinfo->left_ec) || EC_MUST_BE_REDUNDANT(restrictinfo->right_ec)) @@ -1054,27 +1028,17 @@ select_mergejoin_clauses(PlannerInfo *root, } /* - * If it is a right/full join then *all* the explicit join clauses must be - * mergejoinable, else the executor will fail. If we are asked for a right - * join then just return NIL to indicate no mergejoin is possible (we can - * handle it as a left join instead). If we are asked for a full join then - * emit an error, because there is no fallback. + * Report whether mergejoin is allowed (see comment at top of function). */ - if (have_nonmergeable_joinclause) + switch (jointype) { - switch (jointype) - { - case JOIN_RIGHT: - return NIL; /* not mergejoinable */ - case JOIN_FULL: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("FULL JOIN is only supported with merge-joinable join conditions"))); - break; - default: - /* otherwise, it's OK to have nonmergeable join quals */ - break; - } + case JOIN_RIGHT: + case JOIN_FULL: + *mergejoin_allowed = !have_nonmergeable_joinclause; + break; + default: + *mergejoin_allowed = true; + break; } return result_list; diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index e781ad5c1a..24e4e59ea6 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -3,12 +3,12 @@ * joinrels.c * Routines to determine which relations should be joined * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.105 2010/02/26 02:00:45 momjian Exp $ + * src/backend/optimizer/path/joinrels.c * *------------------------------------------------------------------------- */ @@ -17,6 +17,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "utils/memutils.h" static void make_rels_by_clause_joins(PlannerInfo *root, @@ -29,7 +30,8 @@ static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel); static bool is_dummy_rel(RelOptInfo *rel); static void mark_dummy_rel(RelOptInfo *rel); -static bool restriction_is_constant_false(List *restrictlist); +static bool restriction_is_constant_false(List *restrictlist, + bool only_pushed_down); /* @@ -603,7 +605,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) * * Also, a provably constant-false join restriction typically means that * we can skip evaluating one or both sides of the join. We do this by - * marking the appropriate rel as dummy. + * marking the appropriate rel as dummy. For outer joins, a + * constant-false restriction that is pushed down still means the whole + * join is dummy, while a non-pushed-down one means that no inner rows + * will join so we can treat the inner rel as dummy. * * We need only consider the jointypes that appear in join_info_list, plus * JOIN_INNER. @@ -612,7 +617,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) { case JOIN_INNER: if (is_dummy_rel(rel1) || is_dummy_rel(rel2) || - restriction_is_constant_false(restrictlist)) + restriction_is_constant_false(restrictlist, false)) { mark_dummy_rel(joinrel); break; @@ -625,12 +630,13 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) restrictlist); break; case JOIN_LEFT: - if (is_dummy_rel(rel1)) + if (is_dummy_rel(rel1) || + restriction_is_constant_false(restrictlist, true)) { mark_dummy_rel(joinrel); break; } - if (restriction_is_constant_false(restrictlist) && + if (restriction_is_constant_false(restrictlist, false) && bms_is_subset(rel2->relids, sjinfo->syn_righthand)) mark_dummy_rel(rel2); add_paths_to_joinrel(root, joinrel, rel1, rel2, @@ -641,7 +647,8 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) restrictlist); break; case JOIN_FULL: - if (is_dummy_rel(rel1) && is_dummy_rel(rel2)) + if ((is_dummy_rel(rel1) && is_dummy_rel(rel2)) || + restriction_is_constant_false(restrictlist, true)) { mark_dummy_rel(joinrel); break; @@ -652,6 +659,18 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL, sjinfo, restrictlist); + + /* + * If there are join quals that aren't mergeable or hashable, we + * may not be able to build any valid plan. Complain here so that + * we can give a somewhat-useful error message. (Since we have no + * flexibility of planning for a full join, there's no chance of + * succeeding later with another pair of input rels.) + */ + if (joinrel->pathlist == NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("FULL JOIN is only supported with merge-joinable or hash-joinable join conditions"))); break; case JOIN_SEMI: @@ -665,7 +684,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) bms_is_subset(sjinfo->min_righthand, rel2->relids)) { if (is_dummy_rel(rel1) || is_dummy_rel(rel2) || - restriction_is_constant_false(restrictlist)) + restriction_is_constant_false(restrictlist, false)) { mark_dummy_rel(joinrel); break; @@ -687,6 +706,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) create_unique_path(root, rel2, rel2->cheapest_total_path, sjinfo) != NULL) { + if (is_dummy_rel(rel1) || is_dummy_rel(rel2) || + restriction_is_constant_false(restrictlist, false)) + { + mark_dummy_rel(joinrel); + break; + } add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_INNER, sjinfo, restrictlist); @@ -696,12 +721,13 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) } break; case JOIN_ANTI: - if (is_dummy_rel(rel1)) + if (is_dummy_rel(rel1) || + restriction_is_constant_false(restrictlist, true)) { mark_dummy_rel(joinrel); break; } - if (restriction_is_constant_false(restrictlist) && + if (restriction_is_constant_false(restrictlist, false) && bms_is_subset(rel2->relids, sjinfo->syn_righthand)) mark_dummy_rel(rel2); add_paths_to_joinrel(root, joinrel, rel1, rel2, @@ -920,11 +946,32 @@ is_dummy_rel(RelOptInfo *rel) } /* - * Mark a rel as proven empty. + * Mark a relation as proven empty. + * + * During GEQO planning, this can get invoked more than once on the same + * baserel struct, so it's worth checking to see if the rel is already marked + * dummy. + * + * Also, when called during GEQO join planning, we are in a short-lived + * memory context. We must make sure that the dummy path attached to a + * baserel survives the GEQO cycle, else the baserel is trashed for future + * GEQO cycles. On the other hand, when we are marking a joinrel during GEQO, + * we don't want the dummy path to clutter the main planning context. Upshot + * is that the best solution is to explicitly make the dummy path in the same + * context the given RelOptInfo is in. */ static void mark_dummy_rel(RelOptInfo *rel) { + MemoryContext oldcontext; + + /* Already marked? */ + if (is_dummy_rel(rel)) + return; + + /* No, so choose correct context to make the dummy path in */ + oldcontext = MemoryContextSwitchTo(GetMemoryChunkContext(rel)); + /* Set dummy size estimate */ rel->rows = 0; @@ -936,6 +983,8 @@ mark_dummy_rel(RelOptInfo *rel) /* Set or update cheapest_total_path */ set_cheapest(rel); + + MemoryContextSwitchTo(oldcontext); } @@ -947,9 +996,11 @@ mark_dummy_rel(RelOptInfo *rel) * join situations this will leave us computing cartesian products only to * decide there's no match for an outer row, which is pretty stupid. So, * we need to detect the case. + * + * If only_pushed_down is TRUE, then consider only pushed-down quals. */ static bool -restriction_is_constant_false(List *restrictlist) +restriction_is_constant_false(List *restrictlist, bool only_pushed_down) { ListCell *lc; @@ -964,6 +1015,9 @@ restriction_is_constant_false(List *restrictlist) RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Assert(IsA(rinfo, RestrictInfo)); + if (only_pushed_down && !rinfo->is_pushed_down) + continue; + if (rinfo->clause && IsA(rinfo->clause, Const)) { Const *con = (Const *) rinfo->clause; diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index c47fc97063..732ab06363 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -3,12 +3,12 @@ * orindxpath.c * Routines to find index paths that match a set of OR clauses * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.91 2010/01/02 16:57:47 momjian Exp $ + * src/backend/optimizer/path/orindxpath.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 3f0c2fe904..e5228a81c6 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -7,18 +7,17 @@ * the nature and use of path keys. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/pathkeys.c,v 1.101 2010/02/26 02:00:45 momjian Exp $ + * src/backend/optimizer/path/pathkeys.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/skey.h" -#include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" @@ -36,11 +35,6 @@ static PathKey *make_canonical_pathkey(PlannerInfo *root, EquivalenceClass *eclass, Oid opfamily, int strategy, bool nulls_first); static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys); -static PathKey *make_pathkey_from_sortinfo(PlannerInfo *root, - Expr *expr, Oid ordering_op, - bool nulls_first, - Index sortref, - bool canonicalize); static Var *find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, AttrNumber varattno); static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey); @@ -223,86 +217,65 @@ canonicalize_pathkeys(PlannerInfo *root, List *pathkeys) /* * make_pathkey_from_sortinfo - * Given an expression, a sortop, and a nulls-first flag, create - * a PathKey. If canonicalize = true, the result is a "canonical" - * PathKey, otherwise not. (But note it might be redundant anyway.) + * Given an expression and sort-order information, create a PathKey. + * If canonicalize = true, the result is a "canonical" PathKey, + * otherwise not. (But note it might be redundant anyway.) * * If the PathKey is being generated from a SortGroupClause, sortref should be * the SortGroupClause's SortGroupRef; otherwise zero. * + * create_it is TRUE if we should create any missing EquivalenceClass + * needed to represent the sort key. If it's FALSE, we return NULL if the + * sort key isn't already present in any EquivalenceClass. + * * canonicalize should always be TRUE after EquivalenceClass merging has * been performed, but FALSE if we haven't done EquivalenceClass merging yet. */ static PathKey * make_pathkey_from_sortinfo(PlannerInfo *root, - Expr *expr, Oid ordering_op, + Expr *expr, + Oid opfamily, + Oid opcintype, + Oid collation, + bool reverse_sort, bool nulls_first, Index sortref, + bool create_it, bool canonicalize) { - Oid opfamily, - opcintype; int16 strategy; Oid equality_op; List *opfamilies; EquivalenceClass *eclass; + strategy = reverse_sort ? BTGreaterStrategyNumber : BTLessStrategyNumber; + /* - * An ordering operator fully determines the behavior of its opfamily, so - * could only meaningfully appear in one family --- or perhaps two if one - * builds a reverse-sort opfamily, but there's not much point in that - * anymore. But EquivalenceClasses need to contain opfamily lists based - * on the family membership of equality operators, which could easily be - * bigger. So, look up the equality operator that goes with the ordering - * operator (this should be unique) and get its membership. + * EquivalenceClasses need to contain opfamily lists based on the family + * membership of mergejoinable equality operators, which could belong to + * more than one opfamily. So we have to look up the opfamily's equality + * operator and get its membership. */ - - /* Find the operator in pg_amop --- failure shouldn't happen */ - if (!get_ordering_op_properties(ordering_op, - &opfamily, &opcintype, &strategy)) - elog(ERROR, "operator %u is not a valid ordering operator", - ordering_op); - /* Get matching equality operator */ equality_op = get_opfamily_member(opfamily, opcintype, opcintype, BTEqualStrategyNumber); if (!OidIsValid(equality_op)) /* shouldn't happen */ - elog(ERROR, "could not find equality operator for ordering operator %u", - ordering_op); + elog(ERROR, "could not find equality operator for opfamily %u", + opfamily); opfamilies = get_mergejoin_opfamilies(equality_op); if (!opfamilies) /* certainly should find some */ - elog(ERROR, "could not find opfamilies for ordering operator %u", - ordering_op); + elog(ERROR, "could not find opfamilies for equality operator %u", + equality_op); - /* - * When dealing with binary-compatible opclasses, we have to ensure that - * the exposed type of the expression tree matches the declared input type - * of the opclass, except when that is a polymorphic type (compare the - * behavior of parse_coerce.c). This ensures that we can correctly match - * the indexkey or sortclause expression to other expressions we find in - * the query, because arguments of ordinary operator expressions will be - * cast that way. (We have to do this for indexkeys because they are - * represented without any explicit relabel in pg_index, and for sort - * clauses because the parser is likewise cavalier about putting relabels - * on them.) - */ - if (exprType((Node *) expr) != opcintype && - !IsPolymorphicType(opcintype)) - { - /* Strip any existing RelabelType, and add a new one if needed */ - while (expr && IsA(expr, RelabelType)) - expr = (Expr *) ((RelabelType *) expr)->arg; - if (exprType((Node *) expr) != opcintype) - expr = (Expr *) makeRelabelType(expr, - opcintype, - -1, - COERCE_DONTCARE); - } + /* Now find or (optionally) create a matching EquivalenceClass */ + eclass = get_eclass_for_sort_expr(root, expr, opfamilies, + opcintype, collation, + sortref, create_it); - /* Now find or create a matching EquivalenceClass */ - eclass = get_eclass_for_sort_expr(root, expr, opcintype, opfamilies, - sortref); + /* Fail if no EC and !create_it */ + if (!eclass) + return NULL; /* And finally we can find or create a PathKey node */ if (canonicalize) @@ -312,6 +285,48 @@ make_pathkey_from_sortinfo(PlannerInfo *root, return makePathKey(eclass, opfamily, strategy, nulls_first); } +/* + * make_pathkey_from_sortop + * Like make_pathkey_from_sortinfo, but work from a sort operator. + * + * This should eventually go away, but we need to restructure SortGroupClause + * first. + */ +static PathKey * +make_pathkey_from_sortop(PlannerInfo *root, + Expr *expr, + Oid ordering_op, + bool nulls_first, + Index sortref, + bool create_it, + bool canonicalize) +{ + Oid opfamily, + opcintype, + collation; + int16 strategy; + + /* Find the operator in pg_amop --- failure shouldn't happen */ + if (!get_ordering_op_properties(ordering_op, + &opfamily, &opcintype, &strategy)) + elog(ERROR, "operator %u is not a valid ordering operator", + ordering_op); + + /* Because SortGroupClause doesn't carry collation, consult the expr */ + collation = exprCollation((Node *) expr); + + return make_pathkey_from_sortinfo(root, + expr, + opfamily, + opcintype, + collation, + (strategy == BTGreaterStrategyNumber), + nulls_first, + sortref, + create_it, + canonicalize); +} + /**************************************************************************** * PATHKEY COMPARISONS @@ -469,17 +484,19 @@ get_cheapest_fractional_path_for_pathkeys(List *paths, * build_index_pathkeys * Build a pathkeys list that describes the ordering induced by an index * scan using the given index. (Note that an unordered index doesn't - * induce any ordering; such an index will have no sortop OIDS in - * its sortops arrays, and we will return NIL.) + * induce any ordering, so we return NIL.) * - * If 'scandir' is BackwardScanDirection, attempt to build pathkeys - * representing a backwards scan of the index. Return NIL if can't do it. + * If 'scandir' is BackwardScanDirection, build pathkeys representing a + * backwards scan of the index. * * The result is canonical, meaning that redundant pathkeys are removed; * it may therefore have fewer entries than there are index columns. * - * We generate the full pathkeys list whether or not all are useful for the - * current query. Caller should do truncate_useless_pathkeys(). + * Another reason for stopping early is that we may be able to tell that + * an index column's sort order is uninteresting for this query. However, + * that test is just based on the existence of an EquivalenceClass and not + * on position in pathkey lists, so it's not complete. Caller should call + * truncate_useless_pathkeys() to possibly remove more pathkeys. */ List * build_index_pathkeys(PlannerInfo *root, @@ -487,12 +504,16 @@ build_index_pathkeys(PlannerInfo *root, ScanDirection scandir) { List *retval = NIL; - ListCell *indexprs_item = list_head(index->indexprs); + ListCell *indexprs_item; int i; + if (index->sortopfamily == NULL) + return NIL; /* non-orderable index */ + + indexprs_item = list_head(index->indexprs); for (i = 0; i < index->ncolumns; i++) { - Oid sortop; + bool reverse_sort; bool nulls_first; int ikey; Expr *indexkey; @@ -500,18 +521,15 @@ build_index_pathkeys(PlannerInfo *root, if (ScanDirectionIsBackward(scandir)) { - sortop = index->revsortop[i]; + reverse_sort = !index->reverse_sort[i]; nulls_first = !index->nulls_first[i]; } else { - sortop = index->fwdsortop[i]; + reverse_sort = index->reverse_sort[i]; nulls_first = index->nulls_first[i]; } - if (!OidIsValid(sortop)) - break; /* no more orderable columns */ - ikey = index->indexkeys[i]; if (ikey != 0) { @@ -527,14 +545,26 @@ build_index_pathkeys(PlannerInfo *root, indexprs_item = lnext(indexprs_item); } - /* OK, make a canonical pathkey for this sort key */ + /* OK, try to make a canonical pathkey for this sort key */ cpathkey = make_pathkey_from_sortinfo(root, indexkey, - sortop, + index->sortopfamily[i], + index->opcintype[i], + index->indexcollations[i], + reverse_sort, nulls_first, 0, + false, true); + /* + * If the sort key isn't already present in any EquivalenceClass, then + * it's not an interesting sort order for this query. So we can stop + * now --- lower-order sort keys aren't useful either. + */ + if (!cpathkey) + break; + /* Add to list unless redundant */ if (!pathkey_is_redundant(cpathkey, retval)) retval = lappend(retval, cpathkey); @@ -558,7 +588,8 @@ find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, AttrNumber varattno) ListCell *temp; Index relid; Oid reloid, - vartypeid; + vartypeid, + varcollid; int32 type_mod; foreach(temp, rel->reltargetlist) @@ -572,9 +603,9 @@ find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, AttrNumber varattno) relid = rel->relid; reloid = getrelid(relid, root->parse->rtable); - get_atttypetypmod(reloid, varattno, &vartypeid, &type_mod); + get_atttypetypmodcoll(reloid, varattno, &vartypeid, &type_mod, &varcollid); - return makeVar(relid, varattno, vartypeid, type_mod, 0); + return makeVar(relid, varattno, vartypeid, type_mod, varcollid, 0); } /* @@ -629,12 +660,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, Assert(list_length(sub_eclass->ec_members) == 1); sub_member = (EquivalenceMember *) linitial(sub_eclass->ec_members); - outer_expr = (Expr *) - makeVar(rel->relid, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); + outer_expr = (Expr *) makeVarFromTargetEntry(rel->relid, tle); /* * Note: it might look funny to be setting sortref = 0 for a @@ -647,15 +673,23 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, outer_ec = get_eclass_for_sort_expr(root, outer_expr, - sub_member->em_datatype, sub_eclass->ec_opfamilies, - 0); - best_pathkey = - make_canonical_pathkey(root, - outer_ec, - sub_pathkey->pk_opfamily, - sub_pathkey->pk_strategy, - sub_pathkey->pk_nulls_first); + sub_member->em_datatype, + sub_eclass->ec_collation, + 0, + false); + + /* + * If we don't find a matching EC, sub-pathkey isn't + * interesting to the outer query + */ + if (outer_ec) + best_pathkey = + make_canonical_pathkey(root, + outer_ec, + sub_pathkey->pk_opfamily, + sub_pathkey->pk_strategy, + sub_pathkey->pk_nulls_first); } } else @@ -683,23 +717,14 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, { EquivalenceMember *sub_member = (EquivalenceMember *) lfirst(j); Expr *sub_expr = sub_member->em_expr; - Expr *sub_stripped; + Oid sub_expr_type = sub_member->em_datatype; + Oid sub_expr_coll = sub_eclass->ec_collation; ListCell *k; - /* - * We handle two cases: the sub_pathkey key can be either an - * exact match for a targetlist entry, or it could match after - * stripping RelabelType nodes. (We need that case since - * make_pathkey_from_sortinfo could add or remove - * RelabelType.) - */ - sub_stripped = sub_expr; - while (sub_stripped && IsA(sub_stripped, RelabelType)) - sub_stripped = ((RelabelType *) sub_stripped)->arg; - foreach(k, sub_tlist) { TargetEntry *tle = (TargetEntry *) lfirst(k); + Expr *tle_expr; Expr *outer_expr; EquivalenceClass *outer_ec; PathKey *outer_pk; @@ -709,51 +734,41 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, if (tle->resjunk) continue; - if (equal(tle->expr, sub_expr)) - { - /* Exact match */ - outer_expr = (Expr *) - makeVar(rel->relid, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); - } - else - { - Expr *tle_stripped; - - tle_stripped = tle->expr; - while (tle_stripped && IsA(tle_stripped, RelabelType)) - tle_stripped = ((RelabelType *) tle_stripped)->arg; - - if (equal(tle_stripped, sub_stripped)) - { - /* Match after discarding RelabelType */ - outer_expr = (Expr *) - makeVar(rel->relid, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); - if (exprType((Node *) outer_expr) != - exprType((Node *) sub_expr)) - outer_expr = (Expr *) - makeRelabelType(outer_expr, - exprType((Node *) sub_expr), - -1, - COERCE_DONTCARE); - } - else - continue; - } + /* + * The targetlist entry is considered to match if it + * matches after sort-key canonicalization. That is + * needed since the sub_expr has been through the same + * process. + */ + tle_expr = canonicalize_ec_expression(tle->expr, + sub_expr_type, + sub_expr_coll); + if (!equal(tle_expr, sub_expr)) + continue; + + /* + * Build a representation of this targetlist entry as an + * outer Var. + */ + outer_expr = (Expr *) makeVarFromTargetEntry(rel->relid, + tle); - /* Found a representation for this sub_pathkey */ + /* See if we have a matching EC for that */ outer_ec = get_eclass_for_sort_expr(root, outer_expr, - sub_member->em_datatype, sub_eclass->ec_opfamilies, - 0); + sub_expr_type, + sub_expr_coll, + 0, + false); + + /* + * If we don't find a matching EC, this sub-pathkey isn't + * interesting to the outer query + */ + if (!outer_ec) + continue; + outer_pk = make_canonical_pathkey(root, outer_ec, sub_pathkey->pk_opfamily, @@ -869,12 +884,13 @@ make_pathkeys_for_sortclauses(PlannerInfo *root, sortkey = (Expr *) get_sortgroupclause_expr(sortcl, tlist); Assert(OidIsValid(sortcl->sortop)); - pathkey = make_pathkey_from_sortinfo(root, - sortkey, - sortcl->sortop, - sortcl->nulls_first, - sortcl->tleSortGroupRef, - canonicalize); + pathkey = make_pathkey_from_sortop(root, + sortkey, + sortcl->sortop, + sortcl->nulls_first, + sortcl->tleSortGroupRef, + true, + canonicalize); /* Canonical form eliminates redundant ordering keys */ if (canonicalize) @@ -893,46 +909,83 @@ make_pathkeys_for_sortclauses(PlannerInfo *root, ****************************************************************************/ /* - * cache_mergeclause_eclasses - * Make the cached EquivalenceClass links valid in a mergeclause - * restrictinfo. + * initialize_mergeclause_eclasses + * Set the EquivalenceClass links in a mergeclause restrictinfo. * * RestrictInfo contains fields in which we may cache pointers to * EquivalenceClasses for the left and right inputs of the mergeclause. * (If the mergeclause is a true equivalence clause these will be the - * same EquivalenceClass, otherwise not.) + * same EquivalenceClass, otherwise not.) If the mergeclause is either + * used to generate an EquivalenceClass, or derived from an EquivalenceClass, + * then it's easy to set up the left_ec and right_ec members --- otherwise, + * this function should be called to set them up. We will generate new + * EquivalenceClauses if necessary to represent the mergeclause's left and + * right sides. + * + * Note this is called before EC merging is complete, so the links won't + * necessarily point to canonical ECs. Before they are actually used for + * anything, update_mergeclause_eclasses must be called to ensure that + * they've been updated to point to canonical ECs. */ void -cache_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo) +initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo) { + Expr *clause = restrictinfo->clause; + Oid lefttype, + righttype; + + /* Should be a mergeclause ... */ Assert(restrictinfo->mergeopfamilies != NIL); + /* ... with links not yet set */ + Assert(restrictinfo->left_ec == NULL); + Assert(restrictinfo->right_ec == NULL); + + /* Need the declared input types of the operator */ + op_input_types(((OpExpr *) clause)->opno, &lefttype, &righttype); + + /* Find or create a matching EquivalenceClass for each side */ + restrictinfo->left_ec = + get_eclass_for_sort_expr(root, + (Expr *) get_leftop(clause), + restrictinfo->mergeopfamilies, + lefttype, + ((OpExpr *) clause)->inputcollid, + 0, + true); + restrictinfo->right_ec = + get_eclass_for_sort_expr(root, + (Expr *) get_rightop(clause), + restrictinfo->mergeopfamilies, + righttype, + ((OpExpr *) clause)->inputcollid, + 0, + true); +} - /* the cached values should be either both set or both not */ - if (restrictinfo->left_ec == NULL) - { - Expr *clause = restrictinfo->clause; - Oid lefttype, - righttype; - - /* Need the declared input types of the operator */ - op_input_types(((OpExpr *) clause)->opno, &lefttype, &righttype); - - /* Find or create a matching EquivalenceClass for each side */ - restrictinfo->left_ec = - get_eclass_for_sort_expr(root, - (Expr *) get_leftop(clause), - lefttype, - restrictinfo->mergeopfamilies, - 0); - restrictinfo->right_ec = - get_eclass_for_sort_expr(root, - (Expr *) get_rightop(clause), - righttype, - restrictinfo->mergeopfamilies, - 0); - } - else - Assert(restrictinfo->right_ec != NULL); +/* + * update_mergeclause_eclasses + * Make the cached EquivalenceClass links valid in a mergeclause + * restrictinfo. + * + * These pointers should have been set by process_equivalence or + * initialize_mergeclause_eclasses, but they might have been set to + * non-canonical ECs that got merged later. Chase up to the canonical + * merged parent if so. + */ +void +update_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo) +{ + /* Should be a merge clause ... */ + Assert(restrictinfo->mergeopfamilies != NIL); + /* ... with pointers already set */ + Assert(restrictinfo->left_ec != NULL); + Assert(restrictinfo->right_ec != NULL); + + /* Chase up to the top as needed */ + while (restrictinfo->left_ec->ec_merged) + restrictinfo->left_ec = restrictinfo->left_ec->ec_merged; + while (restrictinfo->right_ec->ec_merged) + restrictinfo->right_ec = restrictinfo->right_ec->ec_merged; } /* @@ -968,7 +1021,7 @@ find_mergeclauses_for_pathkeys(PlannerInfo *root, { RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); - cache_mergeclause_eclasses(root, rinfo); + update_mergeclause_eclasses(root, rinfo); } foreach(i, pathkeys) @@ -1104,7 +1157,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, ListCell *lc2; /* get the outer eclass */ - cache_mergeclause_eclasses(root, rinfo); + update_mergeclause_eclasses(root, rinfo); if (rinfo->outer_is_left) oeclass = rinfo->left_ec; @@ -1265,7 +1318,7 @@ make_inner_pathkeys_for_merge(PlannerInfo *root, EquivalenceClass *ieclass; PathKey *pathkey; - cache_mergeclause_eclasses(root, rinfo); + update_mergeclause_eclasses(root, rinfo); if (rinfo->outer_is_left) { @@ -1344,7 +1397,7 @@ make_inner_pathkeys_for_merge(PlannerInfo *root, * that direction should be preferred, in hopes of avoiding a final sort step. * right_merge_direction() implements this heuristic. */ -int +static int pathkeys_useful_for_merging(PlannerInfo *root, RelOptInfo *rel, List *pathkeys) { int useful = 0; @@ -1381,7 +1434,7 @@ pathkeys_useful_for_merging(PlannerInfo *root, RelOptInfo *rel, List *pathkeys) if (restrictinfo->mergeopfamilies == NIL) continue; - cache_mergeclause_eclasses(root, restrictinfo); + update_mergeclause_eclasses(root, restrictinfo); if (pathkey->pk_eclass == restrictinfo->left_ec || pathkey->pk_eclass == restrictinfo->right_ec) @@ -1447,7 +1500,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey) * no good to order by just the first key(s) of the requested ordering. * So the result is always either 0 or list_length(root->query_pathkeys). */ -int +static int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) { if (root->query_pathkeys == NIL) diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 6ea0b2f96f..05c18b5a87 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -25,12 +25,12 @@ * for that. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.35 2010/01/02 16:57:47 momjian Exp $ + * src/backend/optimizer/path/tidpath.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/plan/Makefile b/src/backend/optimizer/plan/Makefile index 3c11972155..88a9f7ff8c 100644 --- a/src/backend/optimizer/plan/Makefile +++ b/src/backend/optimizer/plan/Makefile @@ -4,7 +4,7 @@ # Makefile for optimizer/plan # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/optimizer/plan/Makefile,v 1.16 2010/03/28 22:59:32 tgl Exp $ +# src/backend/optimizer/plan/Makefile # #------------------------------------------------------------------------- diff --git a/src/backend/optimizer/plan/README b/src/backend/optimizer/plan/README index c14cc0a653..013c0f9ea2 100644 --- a/src/backend/optimizer/plan/README +++ b/src/backend/optimizer/plan/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/optimizer/plan/README,v 1.3 2008/03/21 13:23:28 momjian Exp $ +src/backend/optimizer/plan/README Subselects ========== @@ -10,7 +10,7 @@ From owner-pgsql-hackers@hub.org Fri Feb 13 09:01:19 1998 Received: from renoir.op.net (root@renoir.op.net [209.152.193.4]) by candle.pha.pa.us (8.8.5/8.8.5) with ESMTP id JAA11576 for <maillist@candle.pha.pa.us>; Fri, 13 Feb 1998 09:01:17 -0500 (EST) -Received: from hub.org (hub.org [209.47.148.200]) by renoir.op.net (o1/$Revision: 1.3 $) with ESMTP id IAA09761 for <maillist@candle.pha.pa.us>; Fri, 13 Feb 1998 08:41:22 -0500 (EST) +Received: from hub.org (hub.org [209.47.148.200]) by renoir.op.net (o1/$Revision: 1.14 $) with ESMTP id IAA09761 for <maillist@candle.pha.pa.us>; Fri, 13 Feb 1998 08:41:22 -0500 (EST) Received: from localhost (majordom@localhost) by hub.org (8.8.8/8.7.5) with SMTP id IAA08135; Fri, 13 Feb 1998 08:40:17 -0500 (EST) Received: by hub.org (TLB v0.10a (1.23 tibbs 1997/01/09 00:29:32)); Fri, 13 Feb 1998 08:38:42 -0500 (EST) Received: (from majordom@localhost) by hub.org (8.8.8/8.7.5) id IAA06646 for pgsql-hackers-outgoing; Fri, 13 Feb 1998 08:38:35 -0500 (EST) @@ -37,19 +37,19 @@ This is some implementation notes and opened issues... First, implementation uses new type of parameters - PARAM_EXEC - to deal with correlation Vars. When query_planner() is called, it first tries to -replace all upper queries Var referenced in current query with Param of -this type. Some global variables are used to keep mapping of Vars to -Params and Params to Vars. - -After this, all current query' SubLinks are processed: for each SubLink -found in query' qual union_planner() (old planner() function) will be -called to plan corresponding subselect (union_planner() calls -query_planner() for "simple" query and supports UNIONs). After subselect -are planned, optimizer knows about is this correlated, un-correlated or -_undirect_ correlated (references some grand-parent Vars but no parent -ones: uncorrelated from the parent' point of view) query. - -For uncorrelated and undirect correlated subqueries of EXPRession or +replace all upper queries Var referenced in current query with Param of +this type. Some global variables are used to keep mapping of Vars to +Params and Params to Vars. + +After this, all current query' SubLinks are processed: for each SubLink +found in query' qual union_planner() (old planner() function) will be +called to plan corresponding subselect (union_planner() calls +query_planner() for "simple" query and supports UNIONs). After subselect +are planned, optimizer knows about is this correlated, un-correlated or +_undirect_ correlated (references some grand-parent Vars but no parent +ones: uncorrelated from the parent' point of view) query. + +For uncorrelated and undirect correlated subqueries of EXPRession or EXISTS type SubLinks will be replaced with "normal" clauses from SubLink->Oper list (I changed this list to be list of EXPR nodes, not just Oper ones). Right sides of these nodes are replaced with @@ -81,7 +81,7 @@ plan->qual) - to initialize them and let them know about changed Params (from the list of their "interests"). After all SubLinks are processed, query_planner() calls qual' -canonificator and does "normal" work. By using Params optimizer +canonificator and does "normal" work. By using Params optimizer is mostly unchanged. Well, Executor. To get subplans re-evaluated without ExecutorStart() @@ -91,7 +91,7 @@ on each call) ExecReScan() now supports most of Plan types... Explanation of EXPLAIN. -vac=> explain select * from tmp where x >= (select max(x2) from test2 +vac=> explain select * from tmp where x >= (select max(x2) from test2 where y2 = y and exists (select * from tempx where tx = x)); NOTICE: QUERY PLAN: @@ -128,17 +128,17 @@ Opened issues. for each parent tuple - very slow... Results of some test. TMP is table with x,y (int4-s), x in 0-9, -y = 100 - x, 1000 tuples (10 duplicates of each tuple). TEST2 is table +y = 100 - x, 1000 tuples (10 duplicates of each tuple). TEST2 is table with x2, y2 (int4-s), x2 in 1-99, y2 = 100 -x2, 10000 tuples (100 dups). - Trying + Trying select * from tmp where x >= (select max(x2) from test2 where y2 = y); - + and begin; -select y as ty, max(x2) as mx into table tsub from test2, tmp +select y as ty, max(x2) as mx into table tsub from test2, tmp where y2 = y group by ty; vacuum tsub; select x, y from tmp, tsub where x >= mx and y = ty; @@ -156,5 +156,3 @@ SubSelect -> 17 sec (2M of memory) Using temp table -> 32 sec (12M of memory: -S 8192) Vadim - - diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 0d90d072ea..1784ac2fc5 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -11,24 +11,27 @@ * is that we have to work harder to clean up after ourselves when we modify * the query, since the derived data structures have to be updated too. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/analyzejoins.c,v 1.3 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/plan/analyzejoins.c * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" +#include "optimizer/var.h" /* local functions */ static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); -static void remove_rel_from_query(PlannerInfo *root, int relid); +static void remove_rel_from_query(PlannerInfo *root, int relid, + Relids joinrelids); static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); @@ -67,7 +70,9 @@ restart: */ innerrelid = bms_singleton_member(sjinfo->min_righthand); - remove_rel_from_query(root, innerrelid); + remove_rel_from_query(root, innerrelid, + bms_union(sjinfo->min_lefthand, + sjinfo->min_righthand)); /* We verify that exactly one reference gets removed from joinlist */ nremoved = 0; @@ -193,16 +198,23 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * Similarly check that the inner rel doesn't produce any PlaceHolderVars - * that will be used above the join. + * Similarly check that the inner rel isn't needed by any PlaceHolderVars + * that will be used above the join. We only need to fail if such a PHV + * actually references some inner-rel attributes; but the correct check + * for that is relatively expensive, so we first check against ph_eval_at, + * which must mention the inner rel if the PHV uses any inner-rel attrs. */ foreach(l, root->placeholder_list) { PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l); - if (bms_is_subset(phinfo->ph_eval_at, innerrel->relids) && - !bms_is_subset(phinfo->ph_needed, joinrelids)) - return false; + if (bms_is_subset(phinfo->ph_needed, joinrelids)) + continue; /* PHV is not used above the join */ + if (!bms_overlap(phinfo->ph_eval_at, innerrel->relids)) + continue; /* it definitely doesn't reference innerrel */ + if (bms_overlap(pull_varnos((Node *) phinfo->ph_var), + innerrel->relids)) + return false; /* it does reference innerrel */ } /* @@ -216,19 +228,25 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) { RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l); - /* Ignore clauses not pertinent to this join */ - if (!bms_is_subset(restrictinfo->required_relids, joinrelids)) - continue; - /* - * If we find a pushed-down clause, it must have come from above the - * outer join and it must contain references to the inner rel. (If it - * had only outer-rel variables, it'd have been pushed down into the - * outer rel.) Therefore, we can conclude that join removal is unsafe - * without any examination of the clause contents. + * If it's not a join clause for this outer join, we can't use it. + * Note that if the clause is pushed-down, then it is logically from + * above the outer join, even if it references no other rels (it might + * be from WHERE, for example). */ - if (restrictinfo->is_pushed_down) - return false; + if (restrictinfo->is_pushed_down || + !bms_equal(restrictinfo->required_relids, joinrelids)) + { + /* + * If such a clause actually references the inner rel then join + * removal has to be disallowed. We have to check this despite + * the previous attr_needed checks because of the possibility of + * pushed-down clauses referencing the rel. + */ + if (bms_is_member(innerrelid, restrictinfo->clause_relids)) + return false; + continue; /* else, ignore; not useful here */ + } /* Ignore if it's not a mergejoinable clause */ if (!restrictinfo->can_join || @@ -299,14 +317,14 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) * We are not terribly thorough here. We must make sure that the rel is * no longer treated as a baserel, and that attributes of other baserels * are no longer marked as being needed at joins involving this rel. - * In particular, we don't bother removing join quals involving the rel from - * the joininfo lists; they'll just get ignored, since we will never form a - * join relation at which they could be evaluated. + * Also, join quals involving the rel have to be removed from the joininfo + * lists, but only if they belong to the outer join identified by joinrelids. */ static void -remove_rel_from_query(PlannerInfo *root, int relid) +remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) { RelOptInfo *rel = find_base_rel(root, relid); + List *joininfos; Index rti; ListCell *l; @@ -347,8 +365,8 @@ remove_rel_from_query(PlannerInfo *root, int relid) * Likewise remove references from SpecialJoinInfo data structures. * * This is relevant in case the outer join we're deleting is nested inside - * other outer joins: the upper joins' relid sets have to be adjusted. - * The RHS of the target outer join will be made empty here, but that's OK + * other outer joins: the upper joins' relid sets have to be adjusted. The + * RHS of the target outer join will be made empty here, but that's OK * since caller will delete that SpecialJoinInfo entirely. */ foreach(l, root->join_info_list) @@ -378,6 +396,46 @@ remove_rel_from_query(PlannerInfo *root, int relid) phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid); phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid); + /* ph_may_need probably isn't used after this, but fix it anyway */ + phinfo->ph_may_need = bms_del_member(phinfo->ph_may_need, relid); + } + + /* + * Remove any joinquals referencing the rel from the joininfo lists. + * + * In some cases, a joinqual has to be put back after deleting its + * reference to the target rel. This can occur for pseudoconstant and + * outerjoin-delayed quals, which can get marked as requiring the rel in + * order to force them to be evaluated at or above the join. We can't + * just discard them, though. Only quals that logically belonged to the + * outer join being discarded should be removed from the query. + * + * We must make a copy of the rel's old joininfo list before starting the + * loop, because otherwise remove_join_clause_from_rels would destroy the + * list while we're scanning it. + */ + joininfos = list_copy(rel->joininfo); + foreach(l, joininfos) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + + remove_join_clause_from_rels(root, rinfo, rinfo->required_relids); + + if (rinfo->is_pushed_down || + !bms_equal(rinfo->required_relids, joinrelids)) + { + /* Recheck that qual doesn't actually reference the target rel */ + Assert(!bms_is_member(relid, rinfo->clause_relids)); + + /* + * The required_relids probably aren't shared with anything else, + * but let's copy them just to be sure. + */ + rinfo->required_relids = bms_copy(rinfo->required_relids); + rinfo->required_relids = bms_del_member(rinfo->required_relids, + relid); + distribute_restrictinfo_to_rels(root, rinfo); + } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 41c756b0e2..5022e84ee8 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -5,12 +5,12 @@ * Planning is complete, we just need to convert the selected * Path into a Plan. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.275 2010/05/25 17:44:41 tgl Exp $ + * src/backend/optimizer/plan/createplan.c * *------------------------------------------------------------------------- */ @@ -20,14 +20,18 @@ #include <math.h> #include "access/skey.h" +#include "foreign/fdwapi.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/paths.h" #include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/predtest.h" #include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_clause.h" @@ -46,6 +50,7 @@ #include "utils/lsyscache.h" +static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); static Plan *create_scan_plan(PlannerInfo *root, Path *best_path); static List *build_relation_tlist(RelOptInfo *rel); static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel); @@ -53,6 +58,7 @@ static void disuse_physical_tlist(Plan *plan, Path *path); static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals); static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); +static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path); static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path); @@ -93,13 +99,21 @@ static void pgxc_locate_grouping_columns(PlannerInfo *root, List *tlist, static List *pgxc_process_grouping_targetlist(PlannerInfo *root, List **local_tlist); #endif +static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, Plan *outer_plan, Plan *inner_plan); static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); -static List *fix_indexqual_references(List *indexquals, IndexPath *index_path); +static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); +static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); +static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, + List *indexquals); +static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, + List *indexorderbys); +static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_path_costsize(Plan *dest, Path *src); @@ -107,6 +121,7 @@ static void copy_plan_costsize(Plan *dest, Plan *src); static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, Oid indexid, List *indexqual, List *indexqualorig, + List *indexorderby, List *indexorderbyorig, ScanDirection indexscandir); static BitmapIndexScan *make_bitmap_indexscan(Index scanrelid, Oid indexid, List *indexqual, @@ -120,7 +135,8 @@ static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, Index scanrelid, Node *funcexpr, List *funccolnames, - List *funccoltypes, List *funccoltypmods); + List *funccoltypes, List *funccoltypmods, + List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); static CteScan *make_ctescan(List *qptlist, List *qpqual, @@ -131,10 +147,12 @@ static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual, static RemoteQuery *make_remotequery(List *qptlist, RangeTblEntry *rte, List *qpqual, Index scanrelid); #endif +static ForeignScan *make_foreignscan(List *qptlist, List *qpqual, + Index scanrelid, bool fsSystemCol, FdwPlan *fdwplan); static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans); static NestLoop *make_nestloop(List *tlist, - List *joinclauses, List *otherclauses, + List *joinclauses, List *otherclauses, List *nestParams, Plan *lefttree, Plan *righttree, JoinType jointype); static HashJoin *make_hashjoin(List *tlist, @@ -152,13 +170,23 @@ static MergeJoin *make_mergejoin(List *tlist, List *joinclauses, List *otherclauses, List *mergeclauses, Oid *mergefamilies, + Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, Plan *lefttree, Plan *righttree, JoinType jointype); static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst, + AttrNumber *sortColIdx, Oid *sortOperators, + Oid *collations, bool *nullsFirst, double limit_tuples); +static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, + Plan *lefttree, List *pathkeys, + bool adjust_tlist_in_place, + int *p_numsortkeys, + AttrNumber **p_sortColIdx, + Oid **p_sortOperators, + Oid **p_collations, + bool **p_nullsFirst); static Material *make_material(Plan *lefttree); #ifdef PGXC @@ -172,8 +200,8 @@ static void create_remote_expr(PlannerInfo *root, Plan *parent, StringInfo expr, /* * create_plan - * Creates the access plan for a query by tracing backwards through the - * desired chain of pathnodes, starting at the node 'best_path'. For + * Creates the access plan for a query by recursively processing the + * desired tree of pathnodes, starting at the node 'best_path'. For * every pathnode found, we create a corresponding plan node containing * appropriate id, target list, and qualification information. * @@ -190,6 +218,29 @@ create_plan(PlannerInfo *root, Path *best_path) { Plan *plan; + /* Initialize this module's private workspace in PlannerInfo */ + root->curOuterRels = NULL; + root->curOuterParams = NIL; + + /* Recursively process the path tree */ + plan = create_plan_recurse(root, best_path); + + /* Check we successfully assigned all NestLoopParams to plan nodes */ + if (root->curOuterParams != NIL) + elog(ERROR, "failed to assign all NestLoopParams to plan nodes"); + + return plan; +} + +/* + * create_plan_recurse + * Recursive guts of create_plan(). + */ +static Plan * +create_plan_recurse(PlannerInfo *root, Path *best_path) +{ + Plan *plan; + switch (best_path->pathtype) { case T_SeqScan: @@ -201,6 +252,7 @@ create_plan(PlannerInfo *root, Path *best_path) case T_ValuesScan: case T_CteScan: case T_WorkTableScan: + case T_ForeignScan: #ifdef PGXC case T_RemoteQuery: #endif @@ -216,6 +268,10 @@ create_plan(PlannerInfo *root, Path *best_path) plan = create_append_plan(root, (AppendPath *) best_path); break; + case T_MergeAppend: + plan = create_merge_append_plan(root, + (MergeAppendPath *) best_path); + break; case T_Result: plan = (Plan *) create_result_plan(root, (ResultPath *) best_path); @@ -348,6 +404,14 @@ create_scan_plan(PlannerInfo *root, Path *best_path) scan_clauses); break; #endif + + case T_ForeignScan: + plan = (Plan *) create_foreignscan_plan(root, + (ForeignPath *) best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -470,6 +534,7 @@ disuse_physical_tlist(Plan *plan, Path *path) case T_ValuesScan: case T_CteScan: case T_WorkTableScan: + case T_ForeignScan: plan->targetlist = build_relation_tlist(path->parent); break; default: @@ -527,9 +592,16 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) Plan *outer_plan; Plan *inner_plan; Plan *plan; + Relids saveOuterRels = root->curOuterRels; + + outer_plan = create_plan_recurse(root, best_path->outerjoinpath); - outer_plan = create_plan(root, best_path->outerjoinpath); - inner_plan = create_plan(root, best_path->innerjoinpath); + /* For a nestloop, include outer relids in curOuterRels for inner side */ + if (best_path->path.pathtype == T_NestLoop) + root->curOuterRels = bms_union(root->curOuterRels, + best_path->outerjoinpath->parent->relids); + + inner_plan = create_plan_recurse(root, best_path->innerjoinpath); switch (best_path->path.pathtype) { @@ -546,6 +618,10 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) inner_plan); break; case T_NestLoop: + /* Restore curOuterRels */ + bms_free(root->curOuterRels); + root->curOuterRels = saveOuterRels; + plan = (Plan *) create_nestloop_plan(root, (NestPath *) best_path, outer_plan, @@ -1186,10 +1262,9 @@ create_remote_expr(PlannerInfo *root, Plan *parent, StringInfo expr, bms_free(tmprelids); /* Set up deparsing context */ - context = deparse_context_for_plan((Node *) parent, + context = deparse_context_for_planstate((Node *) parent, NULL, - root->parse->rtable, - NULL); + root->parse->rtable); exprstr = deparse_expression(node, context, true, false); @@ -1254,7 +1329,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) { Path *subpath = (Path *) lfirst(subpaths); - subplans = lappend(subplans, create_plan(root, subpath)); + subplans = lappend(subplans, create_plan_recurse(root, subpath)); } plan = make_append(subplans, tlist); @@ -1263,6 +1338,103 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) } /* + * create_merge_append_plan + * Create a MergeAppend plan for 'best_path' and (recursively) plans + * for its subpaths. + * + * Returns a Plan node. + */ +static Plan * +create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) +{ + MergeAppend *node = makeNode(MergeAppend); + Plan *plan = &node->plan; + List *tlist = build_relation_tlist(best_path->path.parent); + List *pathkeys = best_path->path.pathkeys; + List *subplans = NIL; + ListCell *subpaths; + + /* + * We don't have the actual creation of the MergeAppend node split out + * into a separate make_xxx function. This is because we want to run + * prepare_sort_from_pathkeys on it before we do so on the individual + * child plans, to make cross-checking the sort info easier. + */ + copy_path_costsize(plan, (Path *) best_path); + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = NULL; + plan->righttree = NULL; + + /* Compute sort column info, and adjust MergeAppend's tlist as needed */ + (void) prepare_sort_from_pathkeys(root, plan, pathkeys, + true, + &node->numCols, + &node->sortColIdx, + &node->sortOperators, + &node->collations, + &node->nullsFirst); + + /* + * Now prepare the child plans. We must apply prepare_sort_from_pathkeys + * even to subplans that don't need an explicit sort, to make sure they + * are returning the same sort key columns the MergeAppend expects. + */ + foreach(subpaths, best_path->subpaths) + { + Path *subpath = (Path *) lfirst(subpaths); + Plan *subplan; + int numsortkeys; + AttrNumber *sortColIdx; + Oid *sortOperators; + Oid *collations; + bool *nullsFirst; + + /* Build the child plan */ + subplan = create_plan_recurse(root, subpath); + + /* Compute sort column info, and adjust subplan's tlist as needed */ + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, + false, + &numsortkeys, + &sortColIdx, + &sortOperators, + &collations, + &nullsFirst); + + /* + * Check that we got the same sort key information. We just Assert + * that the sortops match, since those depend only on the pathkeys; + * but it seems like a good idea to check the sort column numbers + * explicitly, to ensure the tlists really do match up. + */ + Assert(numsortkeys == node->numCols); + if (memcmp(sortColIdx, node->sortColIdx, + numsortkeys * sizeof(AttrNumber)) != 0) + elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend"); + Assert(memcmp(sortOperators, node->sortOperators, + numsortkeys * sizeof(Oid)) == 0); + Assert(memcmp(collations, node->collations, + numsortkeys * sizeof(Oid)) == 0); + Assert(memcmp(nullsFirst, node->nullsFirst, + numsortkeys * sizeof(bool)) == 0); + + /* Now, insert a Sort node if subplan isn't sufficiently ordered */ + if (!pathkeys_contained_in(pathkeys, subpath->pathkeys)) + subplan = (Plan *) make_sort(root, subplan, numsortkeys, + sortColIdx, sortOperators, + collations, nullsFirst, + best_path->limit_tuples); + + subplans = lappend(subplans, subplan); + } + + node->mergeplans = subplans; + + return (Plan *) node; +} + +/* * create_result_plan * Create a Result plan for 'best_path'. * This is only used for the case of a query with an empty jointree. @@ -1299,7 +1471,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path) Material *plan; Plan *subplan; - subplan = create_plan(root, best_path->subpath); + subplan = create_plan_recurse(root, best_path->subpath); /* We don't want any excess columns in the materialized tuples */ disuse_physical_tlist(subplan, best_path->subpath); @@ -1333,7 +1505,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) int groupColPos; ListCell *l; - subplan = create_plan(root, best_path->subpath); + subplan = create_plan_recurse(root, best_path->subpath); /* Done if we don't need to do any actual unique-ifying */ if (best_path->umethod == UNIQUE_PATH_NOOP) @@ -1450,11 +1622,11 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) build_relation_tlist(best_path->path.parent), NIL, AGG_HASHED, + NULL, numGroupCols, groupColIdx, groupOperators, numGroups, - 0, subplan); } else @@ -1497,6 +1669,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) sortcl->eqop = eqop; sortcl->sortop = sortop; sortcl->nulls_first = false; + sortcl->hashable = false; /* no need to make this accurate */ sortList = lappend(sortList, sortcl); groupColPos++; } @@ -1565,11 +1738,13 @@ create_indexscan_plan(PlannerInfo *root, List *scan_clauses) { List *indexquals = best_path->indexquals; + List *indexorderbys = best_path->indexorderbys; Index baserelid = best_path->path.parent->relid; Oid indexoid = best_path->indexinfo->indexoid; List *qpqual; List *stripped_indexquals; List *fixed_indexquals; + List *fixed_indexorderbys; ListCell *l; IndexScan *scan_plan; @@ -1587,7 +1762,12 @@ create_indexscan_plan(PlannerInfo *root, * The executor needs a copy with the indexkey on the left of each clause * and with index attr numbers substituted for table ones. */ - fixed_indexquals = fix_indexqual_references(indexquals, best_path); + fixed_indexquals = fix_indexqual_references(root, best_path, indexquals); + + /* + * Likewise fix up index attr references in the ORDER BY expressions. + */ + fixed_indexorderbys = fix_indexorderby_references(root, best_path, indexorderbys); /* * If this is an innerjoin scan, the indexclauses will contain join @@ -1658,6 +1838,25 @@ create_indexscan_plan(PlannerInfo *root, /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ qpqual = extract_actual_clauses(qpqual, false); + /* + * We have to replace any outer-relation variables with nestloop params in + * the indexqualorig, qpqual, and indexorderbyorig expressions. A bit + * annoying to have to do this separately from the processing in + * fix_indexqual_references --- rethink this when generalizing the inner + * indexscan support. But note we can't really do this earlier because + * it'd break the comparisons to predicates above ... (or would it? Those + * wouldn't have outer refs) + */ + if (best_path->isjoininner) + { + stripped_indexquals = (List *) + replace_nestloop_params(root, (Node *) stripped_indexquals); + qpqual = (List *) + replace_nestloop_params(root, (Node *) qpqual); + indexorderbys = (List *) + replace_nestloop_params(root, (Node *) indexorderbys); + } + /* Finally ready to build the plan node */ scan_plan = make_indexscan(tlist, qpqual, @@ -1665,6 +1864,8 @@ create_indexscan_plan(PlannerInfo *root, indexoid, fixed_indexquals, stripped_indexquals, + fixed_indexorderbys, + indexorderbys, best_path->indexscandir); copy_path_costsize(&scan_plan->scan.plan, &best_path->path); @@ -1950,6 +2151,18 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, *indexqual = lappend(*indexqual, pred); } } + + /* + * Replace outer-relation variables with nestloop params, but only + * after doing the above comparisons to index predicates. + */ + if (ipath->isjoininner) + { + *qual = (List *) + replace_nestloop_params(root, (Node *) *qual); + *indexqual = (List *) + replace_nestloop_params(root, (Node *) *indexqual); + } } else { @@ -2064,7 +2277,8 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, rte->funcexpr, rte->eref->colnames, rte->funccoltypes, - rte->funccoltypmods); + rte->funccoltypmods, + rte->funccolcollations); copy_path_costsize(&scan_plan->scan.plan, best_path); @@ -2242,6 +2456,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path, return scan_plan; } + #ifdef PGXC /* * create_remotequery_plan @@ -2271,7 +2486,6 @@ create_remotequery_plan(PlannerInfo *root, Path *best_path, StringInfoData sql; RelationLocInfo *rel_loc_info; - Assert(scan_relid > 0); rte = planner_rt_fetch(scan_relid, root); Assert(best_path->parent->rtekind == RTE_RELATION); @@ -2433,6 +2647,55 @@ create_remotequery_plan(PlannerInfo *root, Path *best_path, } #endif +/* + * create_foreignscan_plan + * Returns a foreignscan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static ForeignScan * +create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, + List *tlist, List *scan_clauses) +{ + ForeignScan *scan_plan; + RelOptInfo *rel = best_path->path.parent; + Index scan_relid = rel->relid; + RangeTblEntry *rte; + bool fsSystemCol; + int i; + + /* it should be a base rel... */ + Assert(scan_relid > 0); + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); + + /* Detect whether any system columns are requested from rel */ + fsSystemCol = false; + for (i = rel->min_attr; i < 0; i++) + { + if (!bms_is_empty(rel->attr_needed[i - rel->min_attr])) + { + fsSystemCol = true; + break; + } + } + + scan_plan = make_foreignscan(tlist, + scan_clauses, + scan_relid, + fsSystemCol, + best_path->fdwplan); + + copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + + return scan_plan; +} /***************************************************************************** * @@ -2446,11 +2709,16 @@ create_nestloop_plan(PlannerInfo *root, Plan *outer_plan, Plan *inner_plan) { - List *tlist = build_relation_tlist(best_path->path.parent); - List *joinrestrictclauses = best_path->joinrestrictinfo; - List *joinclauses; - List *otherclauses; - NestLoop *join_plan; + NestLoop *join_plan; + List *tlist = build_relation_tlist(best_path->path.parent); + List *joinrestrictclauses = best_path->joinrestrictinfo; + List *joinclauses; + List *otherclauses; + Relids outerrelids; + List *nestParams; + ListCell *cell; + ListCell *prev; + ListCell *next; /* * If the inner path is a nestloop inner indexscan, it might be using some @@ -2479,9 +2747,32 @@ create_nestloop_plan(PlannerInfo *root, otherclauses = NIL; } + /* + * Identify any nestloop parameters that should be supplied by this join + * node, and move them from root->curOuterParams to the nestParams list. + */ + outerrelids = best_path->outerjoinpath->parent->relids; + nestParams = NIL; + prev = NULL; + for (cell = list_head(root->curOuterParams); cell; cell = next) + { + NestLoopParam *nlp = (NestLoopParam *) lfirst(cell); + + next = lnext(cell); + if (bms_is_member(nlp->paramval->varno, outerrelids)) + { + root->curOuterParams = list_delete_cell(root->curOuterParams, + cell, prev); + nestParams = lappend(nestParams, nlp); + } + else + prev = cell; + } + join_plan = make_nestloop(tlist, joinclauses, otherclauses, + nestParams, outer_plan, inner_plan, best_path->jointype); @@ -2505,6 +2796,7 @@ create_mergejoin_plan(PlannerInfo *root, List *innerpathkeys; int nClauses; Oid *mergefamilies; + Oid *mergecollations; int *mergestrategies; bool *mergenullsfirst; MergeJoin *join_plan; @@ -2596,14 +2888,15 @@ create_mergejoin_plan(PlannerInfo *root, } /* - * Compute the opfamily/strategy/nullsfirst arrays needed by the executor. - * The information is in the pathkeys for the two inputs, but we need to - * be careful about the possibility of mergeclauses sharing a pathkey - * (compare find_mergeclauses_for_pathkeys()). + * Compute the opfamily/collation/strategy/nullsfirst arrays needed by the + * executor. The information is in the pathkeys for the two inputs, but + * we need to be careful about the possibility of mergeclauses sharing a + * pathkey (compare find_mergeclauses_for_pathkeys()). */ nClauses = list_length(mergeclauses); Assert(nClauses == list_length(best_path->path_mergeclauses)); mergefamilies = (Oid *) palloc(nClauses * sizeof(Oid)); + mergecollations = (Oid *) palloc(nClauses * sizeof(Oid)); mergestrategies = (int *) palloc(nClauses * sizeof(int)); mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool)); @@ -2732,12 +3025,14 @@ create_mergejoin_plan(PlannerInfo *root, /* pathkeys should match each other too (more debugging) */ if (opathkey->pk_opfamily != ipathkey->pk_opfamily || + opathkey->pk_eclass->ec_collation != ipathkey->pk_eclass->ec_collation || opathkey->pk_strategy != ipathkey->pk_strategy || opathkey->pk_nulls_first != ipathkey->pk_nulls_first) elog(ERROR, "left and right pathkeys do not match in mergejoin"); /* OK, save info for executor */ mergefamilies[i] = opathkey->pk_opfamily; + mergecollations[i] = opathkey->pk_eclass->ec_collation; mergestrategies[i] = opathkey->pk_strategy; mergenullsfirst[i] = opathkey->pk_nulls_first; i++; @@ -2757,6 +3052,7 @@ create_mergejoin_plan(PlannerInfo *root, otherclauses, mergeclauses, mergefamilies, + mergecollations, mergestrategies, mergenullsfirst, outer_plan, @@ -2890,12 +3186,72 @@ create_hashjoin_plan(PlannerInfo *root, *****************************************************************************/ /* + * replace_nestloop_params + * Replace outer-relation Vars in the given expression with nestloop Params + * + * All Vars belonging to the relation(s) identified by root->curOuterRels + * are replaced by Params, and entries are added to root->curOuterParams if + * not already present. + */ +static Node * +replace_nestloop_params(PlannerInfo *root, Node *expr) +{ + /* No setup needed for tree walk, so away we go */ + return replace_nestloop_params_mutator(expr, root); +} + +static Node * +replace_nestloop_params_mutator(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + Param *param; + NestLoopParam *nlp; + ListCell *lc; + + /* Upper-level Vars should be long gone at this point */ + Assert(var->varlevelsup == 0); + /* If not to be replaced, we can just return the Var unmodified */ + if (!bms_is_member(var->varno, root->curOuterRels)) + return node; + /* Create a Param representing the Var */ + param = assign_nestloop_param(root, var); + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == param->paramid) + { + Assert(equal(var, nlp->paramval)); + /* Present, so we can just return the Param */ + return (Node *) param; + } + } + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = param->paramid; + nlp->paramval = var; + root->curOuterParams = lappend(root->curOuterParams, nlp); + /* And return the replacement Param */ + return (Node *) param; + } + return expression_tree_mutator(node, + replace_nestloop_params_mutator, + (void *) root); +} + +/* * fix_indexqual_references * Adjust indexqual clauses to the form the executor's indexqual * machinery needs. * - * We have three tasks here: + * We have four tasks here: * * Remove RestrictInfo nodes from the input clauses. + * * Replace any outer-relation Var nodes with nestloop Params. + * (XXX eventually, that responsibility should go elsewhere?) * * Index keys must be represented by Var nodes with varattno set to the * index's attribute number, not the attribute number in the original rel. * * If the index key is on the right, commute the clause to put it on the @@ -2907,7 +3263,8 @@ create_hashjoin_plan(PlannerInfo *root, * two separate copies of the subplan tree, or things will go awry). */ static List * -fix_indexqual_references(List *indexquals, IndexPath *index_path) +fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, + List *indexquals) { IndexOptInfo *index = index_path->indexinfo; List *fixed_indexquals; @@ -2915,26 +3272,20 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path) fixed_indexquals = NIL; - /* - * For each qual clause, commute if needed to put the indexkey operand on - * the left, and then fix its varattno. (We do not need to change the - * other side of the clause.) - */ foreach(l, indexquals) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - Expr *clause; + Node *clause; Assert(IsA(rinfo, RestrictInfo)); /* - * Make a copy that will become the fixed clause. + * Replace any outer-relation variables with nestloop params. * - * We used to try to do a shallow copy here, but that fails if there - * is a subplan in the arguments of the opclause. So just do a full - * copy. + * This also makes a copy of the clause, so it's safe to modify it + * in-place below. */ - clause = (Expr *) copyObject((Node *) rinfo->clause); + clause = replace_nestloop_params(root, (Node *) rinfo->clause); if (IsA(clause, OpExpr)) { @@ -3013,12 +3364,67 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path) } /* + * fix_indexorderby_references + * Adjust indexorderby clauses to the form the executor's index + * machinery needs. + * + * This is a simplified version of fix_indexqual_references. The input does + * not have RestrictInfo nodes, and we assume that indxqual.c already + * commuted the clauses to put the index keys on the left. Also, we don't + * bother to support any cases except simple OpExprs, since nothing else + * is allowed for ordering operators. + */ +static List * +fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, + List *indexorderbys) +{ + IndexOptInfo *index = index_path->indexinfo; + List *fixed_indexorderbys; + ListCell *l; + + fixed_indexorderbys = NIL; + + foreach(l, indexorderbys) + { + Node *clause = (Node *) lfirst(l); + + /* + * Replace any outer-relation variables with nestloop params. + * + * This also makes a copy of the clause, so it's safe to modify it + * in-place below. + */ + clause = replace_nestloop_params(root, clause); + + if (IsA(clause, OpExpr)) + { + OpExpr *op = (OpExpr *) clause; + + if (list_length(op->args) != 2) + elog(ERROR, "indexorderby clause is not binary opclause"); + + /* + * Now, determine which index attribute this is and change the + * indexkey operand as needed. + */ + linitial(op->args) = fix_indexqual_operand(linitial(op->args), + index); + } + else + elog(ERROR, "unsupported indexorderby type: %d", + (int) nodeTag(clause)); + + fixed_indexorderbys = lappend(fixed_indexorderbys, clause); + } + + return fixed_indexorderbys; +} + +/* * fix_indexqual_operand * Convert an indexqual expression to a Var referencing the index column. - * - * This is exported because planagg.c needs it. */ -Node * +static Node * fix_indexqual_operand(Node *node, IndexOptInfo *index) { /* @@ -3076,6 +3482,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index) /* Found a match */ result = makeVar(index->rel->relid, pos + 1, exprType(lfirst(indexpr_item)), -1, + exprCollation(lfirst(indexpr_item)), 0); return (Node *) result; } @@ -3122,6 +3529,8 @@ get_switched_clauses(List *clauses, Relids outerrelids) temp->opfuncid = InvalidOid; temp->opresulttype = clause->opresulttype; temp->opretset = clause->opretset; + temp->opcollid = clause->opcollid; + temp->inputcollid = clause->inputcollid; temp->args = list_copy(clause->args); temp->location = clause->location; /* Commute it --- note this modifies the temp node in-place. */ @@ -3226,7 +3635,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses) /* * Copy cost and size info from a Path node to the Plan node created from it. - * The executor won't use this info, but it's needed by EXPLAIN. + * The executor usually won't use this info, but it's needed by EXPLAIN. */ static void copy_path_costsize(Plan *dest, Path *src) @@ -3249,9 +3658,7 @@ copy_path_costsize(Plan *dest, Path *src) /* * Copy cost and size info from a lower plan node to an inserted node. - * This is not critical, since the decisions have already been made, - * but it helps produce more reasonable-looking EXPLAIN output. - * (Some callers alter the info after copying it.) + * (Most callers alter the info after copying it.) */ static void copy_plan_costsize(Plan *dest, Plan *src) @@ -3307,6 +3714,8 @@ make_indexscan(List *qptlist, Oid indexid, List *indexqual, List *indexqualorig, + List *indexorderby, + List *indexorderbyorig, ScanDirection indexscandir) { IndexScan *node = makeNode(IndexScan); @@ -3321,6 +3730,8 @@ make_indexscan(List *qptlist, node->indexid = indexid; node->indexqual = indexqual; node->indexqualorig = indexqualorig; + node->indexorderby = indexorderby; + node->indexorderbyorig = indexorderbyorig; node->indexorderdir = indexscandir; return node; @@ -3427,7 +3838,8 @@ make_functionscan(List *qptlist, Node *funcexpr, List *funccolnames, List *funccoltypes, - List *funccoltypmods) + List *funccoltypmods, + List *funccolcollations) { FunctionScan *node = makeNode(FunctionScan); Plan *plan = &node->scan.plan; @@ -3442,6 +3854,7 @@ make_functionscan(List *qptlist, node->funccolnames = funccolnames; node->funccoltypes = funccoltypes; node->funccoltypmods = funccoltypmods; + node->funccolcollations = funccolcollations; return node; } @@ -3508,6 +3921,7 @@ make_worktablescan(List *qptlist, return node; } + #ifdef PGXC static RemoteQuery * make_remotequery(List *qptlist, @@ -3530,6 +3944,30 @@ make_remotequery(List *qptlist, } #endif +static ForeignScan * +make_foreignscan(List *qptlist, + List *qpqual, + Index scanrelid, + bool fsSystemCol, + FdwPlan *fdwplan) +{ + ForeignScan *node = makeNode(ForeignScan); + + Plan *plan = &node->scan.plan; + + /* cost should be inserted by caller */ + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->fsSystemCol = fsSystemCol; + node->fdwplan = fdwplan; + + return node; +} + + Append * make_append(List *appendplans, List *tlist) { @@ -3542,6 +3980,12 @@ make_append(List *appendplans, List *tlist) * Compute cost as sum of subplan costs. We charge nothing extra for the * Append itself, which perhaps is too optimistic, but since it doesn't do * any selection or projection, it is a pretty cheap node. + * + * If you change this, see also create_append_path(). Also, the size + * calculations should match set_append_rel_pathlist(). It'd be better + * not to duplicate all this logic, but some callers of this function + * aren't working from an appendrel or AppendPath, so there's noplace to + * copy the data from. */ plan->startup_cost = 0; plan->total_cost = 0; @@ -3661,6 +4105,7 @@ static NestLoop * make_nestloop(List *tlist, List *joinclauses, List *otherclauses, + List *nestParams, Plan *lefttree, Plan *righttree, JoinType jointype) @@ -3675,6 +4120,7 @@ make_nestloop(List *tlist, plan->righttree = righttree; node->join.jointype = jointype; node->join.joinqual = joinclauses; + node->nestParams = nestParams; return node; } @@ -3741,6 +4187,7 @@ make_mergejoin(List *tlist, List *otherclauses, List *mergeclauses, Oid *mergefamilies, + Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, Plan *lefttree, @@ -3757,6 +4204,7 @@ make_mergejoin(List *tlist, plan->righttree = righttree; node->mergeclauses = mergeclauses; node->mergeFamilies = mergefamilies; + node->mergeCollations = mergecollations; node->mergeStrategies = mergestrategies; node->mergeNullsFirst = mergenullsfirst; node->join.jointype = jointype; @@ -3768,13 +4216,14 @@ make_mergejoin(List *tlist, /* * make_sort --- basic routine to build a Sort plan node * - * Caller must have built the sortColIdx, sortOperators, and nullsFirst - * arrays already. limit_tuples is as for cost_sort (in particular, pass - * -1 if no limit) + * Caller must have built the sortColIdx, sortOperators, collations, and + * nullsFirst arrays already. + * limit_tuples is as for cost_sort (in particular, pass -1 if no limit) */ static Sort * make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst, + AttrNumber *sortColIdx, Oid *sortOperators, + Oid *collations, bool *nullsFirst, double limit_tuples) { Sort *node = makeNode(Sort); @@ -3786,6 +4235,8 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, lefttree->total_cost, lefttree->plan_rows, lefttree->plan_width, + 0.0, + work_mem, limit_tuples); plan->startup_cost = sort_path.startup_cost; plan->total_cost = sort_path.total_cost; @@ -3796,6 +4247,7 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, node->numCols = numCols; node->sortColIdx = sortColIdx; node->sortOperators = sortOperators; + node->collations = collations; node->nullsFirst = nullsFirst; return node; @@ -3811,9 +4263,9 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, * max possible number of columns. Return value is the new column count. */ static int -add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, +add_sort_column(AttrNumber colIdx, Oid sortOp, Oid coll, bool nulls_first, int numCols, AttrNumber *sortColIdx, - Oid *sortOperators, bool *nullsFirst) + Oid *sortOperators, Oid *collations, bool *nullsFirst) { int i; @@ -3827,9 +4279,15 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, * values that < considers equal. We need not check nulls_first * however because a lower-order column with the same sortop but * opposite nulls direction is redundant. + * + * We could probably consider sort keys with the same sortop and + * different collations to be redundant too, but for the moment treat + * them as not redundant. This will be needed if we ever support + * collations with different notions of equality. */ if (sortColIdx[i] == colIdx && - sortOperators[numCols] == sortOp) + sortOperators[numCols] == sortOp && + collations[numCols] == coll) { /* Already sorting by this col, so extra sort key is useless */ return numCols; @@ -3839,37 +4297,56 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, /* Add the column */ sortColIdx[numCols] = colIdx; sortOperators[numCols] = sortOp; + collations[numCols] = coll; nullsFirst[numCols] = nulls_first; return numCols + 1; } /* - * make_sort_from_pathkeys - * Create sort plan to sort according to given pathkeys + * prepare_sort_from_pathkeys + * Prepare to sort according to given pathkeys * + * This is used to set up for both Sort and MergeAppend nodes. It calculates + * the executor's representation of the sort key information, and adjusts the + * plan targetlist if needed to add resjunk sort columns. + * + * Input parameters: * 'lefttree' is the node which yields input tuples * 'pathkeys' is the list of pathkeys by which the result is to be sorted - * 'limit_tuples' is the bound on the number of output tuples; - * -1 if no bound + * 'adjust_tlist_in_place' is TRUE if lefttree must be modified in-place * * We must convert the pathkey information into arrays of sort key column - * numbers and sort operator OIDs. + * numbers, sort operator OIDs, collation OIDs, and nulls-first flags, + * which is the representation the executor wants. These are returned into + * the output parameters *p_numsortkeys etc. * * If the pathkeys include expressions that aren't simple Vars, we will * usually need to add resjunk items to the input plan's targetlist to - * compute these expressions (since the Sort node itself won't do it). - * If the input plan type isn't one that can do projections, this means - * adding a Result node just to do the projection. + * compute these expressions, since the Sort/MergeAppend node itself won't + * do any such calculations. If the input plan type isn't one that can do + * projections, this means adding a Result node just to do the projection. + * However, the caller can pass adjust_tlist_in_place = TRUE to force the + * lefttree tlist to be modified in-place regardless of whether the node type + * can project --- we use this for fixing the tlist of MergeAppend itself. + * + * Returns the node which is to be the input to the Sort (either lefttree, + * or a Result stacked atop lefttree). */ -Sort * -make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, - double limit_tuples) +static Plan * +prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, + bool adjust_tlist_in_place, + int *p_numsortkeys, + AttrNumber **p_sortColIdx, + Oid **p_sortOperators, + Oid **p_collations, + bool **p_nullsFirst) { List *tlist = lefttree->targetlist; ListCell *i; int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -3878,6 +4355,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, numsortkeys = list_length(pathkeys); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -3927,7 +4405,12 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, { EquivalenceMember *em = (EquivalenceMember *) lfirst(j); - if (em->em_is_const || em->em_is_child) + /* + * We shouldn't be trying to sort by an equivalence class that + * contains a constant, so no need to consider such cases any + * further. + */ + if (em->em_is_const) continue; tle = tlist_member((Node *) em->em_expr, tlist); @@ -3963,7 +4446,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, List *exprvars; ListCell *k; - if (em->em_is_const || em->em_is_child) + if (em->em_is_const) continue; sortexpr = em->em_expr; exprvars = pull_var_clause((Node *) sortexpr, @@ -3986,7 +4469,8 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, /* * Do we need to insert a Result node? */ - if (!is_projection_capable_plan(lefttree)) + if (!adjust_tlist_in_place && + !is_projection_capable_plan(lefttree)) { /* copy needed so we don't modify input's tlist below */ tlist = copyObject(tlist); @@ -3994,6 +4478,9 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, lefttree); } + /* Don't bother testing is_projection_capable_plan again */ + adjust_tlist_in_place = true; + /* * Add resjunk entry to input's tlist */ @@ -4027,15 +4514,57 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, */ numsortkeys = add_sort_column(tle->resno, sortop, + pathkey->pk_eclass->ec_collation, pathkey->pk_nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, + collations, nullsFirst); } Assert(numsortkeys > 0); + /* Return results */ + *p_numsortkeys = numsortkeys; + *p_sortColIdx = sortColIdx; + *p_sortOperators = sortOperators; + *p_collations = collations; + *p_nullsFirst = nullsFirst; + + return lefttree; +} + +/* + * make_sort_from_pathkeys + * Create sort plan to sort according to given pathkeys + * + * 'lefttree' is the node which yields input tuples + * 'pathkeys' is the list of pathkeys by which the result is to be sorted + * 'limit_tuples' is the bound on the number of output tuples; + * -1 if no bound + */ +Sort * +make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, + double limit_tuples) +{ + int numsortkeys; + AttrNumber *sortColIdx; + Oid *sortOperators; + Oid *collations; + bool *nullsFirst; + + /* Compute sort column info, and adjust lefttree as needed */ + lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys, + false, + &numsortkeys, + &sortColIdx, + &sortOperators, + &collations, + &nullsFirst); + + /* Now build the Sort node */ return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, limit_tuples); + sortColIdx, sortOperators, collations, + nullsFirst, limit_tuples); } /* @@ -4053,6 +4582,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -4061,6 +4591,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) numsortkeys = list_length(sortcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -4076,15 +4607,18 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) * redundantly. */ numsortkeys = add_sort_column(tle->resno, sortcl->sortop, + exprCollation((Node *) tle->expr), sortcl->nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, + collations, nullsFirst); } Assert(numsortkeys > 0); return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, -1.0); + sortColIdx, sortOperators, collations, + nullsFirst, -1.0); } /* @@ -4112,6 +4646,7 @@ make_sort_from_groupcols(PlannerInfo *root, int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -4120,6 +4655,7 @@ make_sort_from_groupcols(PlannerInfo *root, numsortkeys = list_length(groupcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -4135,16 +4671,19 @@ make_sort_from_groupcols(PlannerInfo *root, * redundantly. */ numsortkeys = add_sort_column(tle->resno, grpcl->sortop, + exprCollation((Node *) tle->expr), grpcl->nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, + collations, nullsFirst); grpno++; } Assert(numsortkeys > 0); return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, -1.0); + sortColIdx, sortOperators, collations, + nullsFirst, -1.0); } static Material * @@ -4200,9 +4739,9 @@ materialize_finished_plan(Plan *subplan) Agg * make_agg(PlannerInfo *root, List *tlist, List *qual, - AggStrategy aggstrategy, + AggStrategy aggstrategy, const AggClauseCosts *aggcosts, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, - long numGroups, int numAggs, + long numGroups, Plan *lefttree) { Agg *node = makeNode(Agg); @@ -4218,7 +4757,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, copy_plan_costsize(plan, lefttree); /* only care about copying size */ cost_agg(&agg_path, root, - aggstrategy, numAggs, + aggstrategy, aggcosts, numGroupCols, numGroups, lefttree->startup_cost, lefttree->total_cost, @@ -4266,7 +4805,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, WindowAgg * make_windowagg(PlannerInfo *root, List *tlist, - int numWindowFuncs, Index winref, + List *windowFuncs, Index winref, int partNumCols, AttrNumber *partColIdx, Oid *partOperators, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, int frameOptions, Node *startOffset, Node *endOffset, @@ -4290,7 +4829,7 @@ make_windowagg(PlannerInfo *root, List *tlist, copy_plan_costsize(plan, lefttree); /* only care about copying size */ cost_windowagg(&windowagg_path, root, - numWindowFuncs, partNumCols, ordNumCols, + windowFuncs, partNumCols, ordNumCols, lefttree->startup_cost, lefttree->total_cost, lefttree->plan_rows); @@ -4662,7 +5201,8 @@ make_result(PlannerInfo *root, * to make it look better sometime. */ ModifyTable * -make_modifytable(CmdType operation, List *resultRelations, +make_modifytable(CmdType operation, bool canSetTag, + List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam) { @@ -4712,7 +5252,9 @@ make_modifytable(CmdType operation, List *resultRelations, node->plan.targetlist = NIL; node->operation = operation; + node->canSetTag = canSetTag; node->resultRelations = resultRelations; + node->resultRelIndex = -1; /* will be set correctly in setrefs.c */ node->plans = subplans; node->returningLists = returningLists; node->rowMarks = rowMarks; @@ -4740,6 +5282,7 @@ is_projection_capable_plan(Plan *plan) case T_Limit: case T_ModifyTable: case T_Append: + case T_MergeAppend: case T_RecursiveUnion: return false; default: @@ -4988,7 +5531,7 @@ create_remotedelete_plan(PlannerInfo *root, Plan *topplan) appendStringInfo(buf, " %s = $%d", NameStr(att_tup->attname), att); expr = makeVar(att, att, att_tup->atttypid, - att_tup->atttypmod, 0); + att_tup->atttypmod, InvalidOid, 0); tle = makeTargetEntry((Expr *) expr, att, NameStr(att_tup->attname), false); xtlist = lappend(xtlist, tle); @@ -5018,7 +5561,7 @@ create_remotedelete_plan(PlannerInfo *root, Plan *topplan) xstep->exec_nodes->accesstype = RELATION_ACCESS_READ; /* First and only target entry of topplan is ctid, reference it */ - ctid = makeVar(INNER, 1, TIDOID, -1, 0); + ctid = makeVar(INNER, 1, TIDOID, -1, InvalidOid, 0); xstep->exec_nodes->expr = (Expr *) ctid; pfree(xbuf->data); @@ -5048,7 +5591,7 @@ create_remotedelete_plan(PlannerInfo *root, Plan *topplan) fstep->exec_nodes->accesstype = RELATION_ACCESS_UPDATE; /* First and only target entry of topplan is ctid, reference it */ - ctid = makeVar(INNER, 1, TIDOID, -1, 0); + ctid = makeVar(INNER, 1, TIDOID, -1, InvalidOid, 0); fstep->exec_nodes->expr = (Expr *) ctid; } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index f8e1d523bb..333ede218e 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -3,12 +3,12 @@ * initsplan.c * Target list, qualification, joininfo initialization routines * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.158 2010/02/26 02:00:45 momjian Exp $ + * src/backend/optimizer/plan/initsplan.c * *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_type.h" +#include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/joininfo.h" @@ -187,6 +188,14 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) phinfo->ph_needed = bms_add_members(phinfo->ph_needed, where_needed); + + /* + * Update ph_may_need too. This is currently only necessary when + * being called from build_base_rel_tlists, but we may as well do + * it always. + */ + phinfo->ph_may_need = bms_add_members(phinfo->ph_may_need, + where_needed); } else elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -465,7 +474,11 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, /* Now we can add the SpecialJoinInfo to join_info_list */ if (sjinfo) + { root->join_info_list = lappend(root->join_info_list, sjinfo); + /* Each time we do that, recheck placeholder eval levels */ + update_placeholder_eval_levels(root, sjinfo); + } /* * Finally, compute the output joinlist. We fold subproblems together @@ -688,6 +701,32 @@ make_outerjoininfo(PlannerInfo *root, } /* + * Examine PlaceHolderVars. If a PHV is supposed to be evaluated within + * this join's nullable side, and it may get used above this join, then + * ensure that min_righthand contains the full eval_at set of the PHV. + * This ensures that the PHV actually can be evaluated within the RHS. + * Note that this works only because we should already have determined the + * final eval_at level for any PHV syntactically within this join. + */ + foreach(l, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l); + Relids ph_syn_level = phinfo->ph_var->phrels; + + /* Ignore placeholder if it didn't syntactically come from RHS */ + if (!bms_is_subset(ph_syn_level, right_rels)) + continue; + + /* We can also ignore it if it's certainly not used above this join */ + /* XXX this test is probably overly conservative */ + if (bms_is_subset(phinfo->ph_may_need, min_righthand)) + continue; + + /* Else, prevent join from being formed before we eval the PHV */ + min_righthand = bms_add_members(min_righthand, phinfo->ph_eval_at); + } + + /* * If we found nothing to put in min_lefthand, punt and make it the full * LHS, to avoid having an empty min_lefthand which will confuse later * processing. (We don't try to be smart about such cases, just correct.) @@ -1029,6 +1068,12 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * * If none of the above hold, pass it off to * distribute_restrictinfo_to_rels(). + * + * In all cases, it's important to initialize the left_ec and right_ec + * fields of a mergejoinable clause, so that all possibly mergejoinable + * expressions have representations in EquivalenceClasses. If + * process_equivalence is successful, it will take care of that; + * otherwise, we have to call initialize_mergeclause_eclasses to do it. */ if (restrictinfo->mergeopfamilies) { @@ -1036,10 +1081,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, { if (process_equivalence(root, restrictinfo, below_outer_join)) return; - /* EC rejected it, so pass to distribute_restrictinfo_to_rels */ + /* EC rejected it, so set left_ec/right_ec the hard way ... */ + initialize_mergeclause_eclasses(root, restrictinfo); + /* ... and fall through to distribute_restrictinfo_to_rels */ } else if (maybe_outer_join && restrictinfo->can_join) { + /* we need to set up left_ec/right_ec the hard way */ + initialize_mergeclause_eclasses(root, restrictinfo); + /* now see if it should go to any outer-join lists */ if (bms_is_subset(restrictinfo->left_relids, outerjoin_nonnullable) && !bms_overlap(restrictinfo->right_relids, @@ -1067,6 +1117,12 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, restrictinfo); return; } + /* nope, so fall through to distribute_restrictinfo_to_rels */ + } + else + { + /* we still need to set up left_ec/right_ec */ + initialize_mergeclause_eclasses(root, restrictinfo); } } @@ -1273,10 +1329,9 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, /* * Check for hashjoinable operators. (We don't bother setting the - * hashjoin info if we're not going to need it.) + * hashjoin info except in true join clauses.) */ - if (enable_hashjoin) - check_hashjoinable(restrictinfo); + check_hashjoinable(restrictinfo); /* * Add clause to the join lists of all the relevant relations. @@ -1317,6 +1372,7 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, void process_implied_equality(PlannerInfo *root, Oid opno, + Oid collation, Expr *item1, Expr *item2, Relids qualscope, @@ -1333,7 +1389,9 @@ process_implied_equality(PlannerInfo *root, BOOLOID, /* opresulttype */ false, /* opretset */ (Expr *) copyObject(item1), - (Expr *) copyObject(item2)); + (Expr *) copyObject(item2), + InvalidOid, + collation); /* If both constant, try to reduce to a boolean constant. */ if (both_const) @@ -1367,9 +1425,13 @@ process_implied_equality(PlannerInfo *root, * * This overlaps the functionality of process_implied_equality(), but we * must return the RestrictInfo, not push it into the joininfo tree. + * + * Note: we do not do initialize_mergeclause_eclasses() here. It is + * caller's responsibility that left_ec/right_ec be set as necessary. */ RestrictInfo * build_implied_join_equality(Oid opno, + Oid collation, Expr *item1, Expr *item2, Relids qualscope) @@ -1385,7 +1447,9 @@ build_implied_join_equality(Oid opno, BOOLOID, /* opresulttype */ false, /* opretset */ (Expr *) copyObject(item1), - (Expr *) copyObject(item2)); + (Expr *) copyObject(item2), + InvalidOid, + collation); /* Make a copy of qualscope to avoid problems if source EC changes */ qualscope = bms_copy(qualscope); @@ -1400,10 +1464,9 @@ build_implied_join_equality(Oid opno, qualscope, /* required_relids */ NULL); /* nullable_relids */ - /* Set mergejoinability info always, and hashjoinability if enabled */ + /* Set mergejoinability/hashjoinability flags */ check_mergejoinable(restrictinfo); - if (enable_hashjoin) - check_hashjoinable(restrictinfo); + check_hashjoinable(restrictinfo); return restrictinfo; } @@ -1429,6 +1492,7 @@ check_mergejoinable(RestrictInfo *restrictinfo) { Expr *clause = restrictinfo->clause; Oid opno; + Node *leftarg; if (restrictinfo->pseudoconstant) return; @@ -1438,8 +1502,9 @@ check_mergejoinable(RestrictInfo *restrictinfo) return; opno = ((OpExpr *) clause)->opno; + leftarg = linitial(((OpExpr *) clause)->args); - if (op_mergejoinable(opno) && + if (op_mergejoinable(opno, exprType(leftarg)) && !contain_volatile_functions((Node *) clause)) restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno); @@ -1464,6 +1529,7 @@ check_hashjoinable(RestrictInfo *restrictinfo) { Expr *clause = restrictinfo->clause; Oid opno; + Node *leftarg; if (restrictinfo->pseudoconstant) return; @@ -1473,8 +1539,9 @@ check_hashjoinable(RestrictInfo *restrictinfo) return; opno = ((OpExpr *) clause)->opno; + leftarg = linitial(((OpExpr *) clause)->args); - if (op_hashjoinable(opno) && + if (op_hashjoinable(opno, exprType(leftarg)) && !contain_volatile_functions((Node *) clause)) restrictinfo->hashjoinoperator = opno; } diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 369ed9c929..2f5955706a 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -3,92 +3,81 @@ * planagg.c * Special planning for aggregate queries. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * This module tries to replace MIN/MAX aggregate functions by subqueries + * of the form + * (SELECT col FROM tab + * WHERE col IS NOT NULL AND existing-quals + * ORDER BY col ASC/DESC + * LIMIT 1) + * Given a suitable index on tab.col, this can be much faster than the + * generic scan-all-the-rows aggregation plan. We can handle multiple + * MIN/MAX aggregates by generating multiple subqueries, and their + * orderings can be different. However, if the query contains any + * non-optimizable aggregates, there's no point since we'll have to + * scan all the rows anyway. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.53 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/plan/planagg.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/pg_aggregate.h" -#include "catalog/pg_am.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" -#include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" -#include "optimizer/predtest.h" #include "optimizer/subselect.h" -#include "parser/parse_clause.h" #include "parser/parsetree.h" +#include "parser/parse_clause.h" #include "utils/lsyscache.h" #include "utils/syscache.h" -typedef struct -{ - Oid aggfnoid; /* pg_proc Oid of the aggregate */ - Oid aggsortop; /* Oid of its sort operator */ - Expr *target; /* expression we are aggregating on */ - NullTest *notnulltest; /* expression for "target IS NOT NULL" */ - IndexPath *path; /* access path for index scan */ - Cost pathcost; /* estimated cost to fetch first row */ - bool nulls_first; /* null ordering direction matching index */ - Param *param; /* param for subplan's output */ -} MinMaxAggInfo; - static bool find_minmax_aggs_walker(Node *node, List **context); -static bool build_minmax_path(PlannerInfo *root, RelOptInfo *rel, - MinMaxAggInfo *info); -static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info, - IndexOptInfo *index, int indexcol); -static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info); -static void attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan); -static Node *replace_aggs_with_params_mutator(Node *node, List **context); +static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, + Oid eqop, Oid sortop, bool nulls_first); +static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo); +static Node *replace_aggs_with_params_mutator(Node *node, PlannerInfo *root); static Oid fetch_agg_sort_op(Oid aggfnoid); /* - * optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes + * preprocess_minmax_aggregates - preprocess MIN/MAX aggregates * - * This checks to see if we can replace MIN/MAX aggregate functions by - * subqueries of the form - * (SELECT col FROM tab WHERE ... ORDER BY col ASC/DESC LIMIT 1) - * Given a suitable index on tab.col, this can be much faster than the - * generic scan-all-the-rows plan. + * Check to see whether the query contains MIN/MAX aggregate functions that + * might be optimizable via indexscans. If it does, and all the aggregates + * are potentially optimizable, then set up root->minmax_aggs with a list of + * these aggregates. * - * We are passed the preprocessed tlist, and the best path - * devised for computing the input of a standard Agg node. If we are able - * to optimize all the aggregates, and the result is estimated to be cheaper - * than the generic aggregate method, then generate and return a Plan that - * does it that way. Otherwise, return NULL. + * Note: we are passed the preprocessed targetlist separately, because it's + * not necessarily equal to root->parse->targetList. */ -Plan * -optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) +void +preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) { Query *parse = root->parse; FromExpr *jtnode; RangeTblRef *rtr; RangeTblEntry *rte; - RelOptInfo *rel; List *aggs_list; - ListCell *l; - Cost total_cost; - Path agg_p; - Plan *plan; - Node *hqual; - QualCost tlist_cost; + ListCell *lc; + + /* minmax_aggs list should be empty at this point */ + Assert(root->minmax_aggs == NIL); /* Nothing to do if query has no aggregates */ if (!parse->hasAggs) - return NULL; + return; Assert(!parse->setOperations); /* shouldn't get here if a setop */ Assert(parse->rowMarks == NIL); /* nor if FOR UPDATE */ @@ -98,72 +87,143 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) * * We don't handle GROUP BY or windowing, because our current * implementations of grouping require looking at all the rows anyway, and - * so there's not much point in optimizing MIN/MAX. + * so there's not much point in optimizing MIN/MAX. (Note: relaxing this + * would likely require some restructuring in grouping_planner(), since it + * performs assorted processing related to these features between calling + * preprocess_minmax_aggregates and optimize_minmax_aggregates.) */ if (parse->groupClause || parse->hasWindowFuncs) - return NULL; + return; /* * We also restrict the query to reference exactly one table, since join * conditions can't be handled reasonably. (We could perhaps handle a * query containing cartesian-product joins, but it hardly seems worth the * trouble.) However, the single real table could be buried in several - * levels of FromExpr. + * levels of FromExpr due to subqueries. Note the single table could be + * an inheritance parent, too. */ jtnode = parse->jointree; while (IsA(jtnode, FromExpr)) { if (list_length(jtnode->fromlist) != 1) - return NULL; + return; jtnode = linitial(jtnode->fromlist); } if (!IsA(jtnode, RangeTblRef)) - return NULL; + return; rtr = (RangeTblRef *) jtnode; rte = planner_rt_fetch(rtr->rtindex, root); - if (rte->rtekind != RTE_RELATION || rte->inh) - return NULL; - rel = find_base_rel(root, rtr->rtindex); + if (rte->rtekind != RTE_RELATION) + return; /* - * Since this optimization is not applicable all that often, we want to - * fall out before doing very much work if possible. Therefore we do the - * work in several passes. The first pass scans the tlist and HAVING qual - * to find all the aggregates and verify that each of them is a MIN/MAX - * aggregate. If that succeeds, the second pass looks at each aggregate - * to see if it is optimizable; if so we make an IndexPath describing how - * we would scan it. (We do not try to optimize if only some aggs are - * optimizable, since that means we'll have to scan all the rows anyway.) - * If that succeeds, we have enough info to compare costs against the - * generic implementation. Only if that test passes do we build a Plan. + * Scan the tlist and HAVING qual to find all the aggregates and verify + * all are MIN/MAX aggregates. Stop as soon as we find one that isn't. */ - - /* Pass 1: find all the aggregates */ aggs_list = NIL; if (find_minmax_aggs_walker((Node *) tlist, &aggs_list)) - return NULL; + return; if (find_minmax_aggs_walker(parse->havingQual, &aggs_list)) - return NULL; + return; - /* Pass 2: see if each one is optimizable */ - total_cost = 0; - foreach(l, aggs_list) + /* + * OK, there is at least the possibility of performing the optimization. + * Build an access path for each aggregate. (We must do this now because + * we need to call query_planner with a pristine copy of the current query + * tree; it'll be too late when optimize_minmax_aggregates gets called.) + * If any of the aggregates prove to be non-indexable, give up; there is + * no point in optimizing just some of them. + */ + foreach(lc, aggs_list) { - MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l); + MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); + Oid eqop; + bool reverse; + + /* + * We'll need the equality operator that goes with the aggregate's + * ordering operator. + */ + eqop = get_equality_op_for_ordering_op(mminfo->aggsortop, &reverse); + if (!OidIsValid(eqop)) /* shouldn't happen */ + elog(ERROR, "could not find equality operator for ordering operator %u", + mminfo->aggsortop); + + /* + * We can use either an ordering that gives NULLS FIRST or one that + * gives NULLS LAST; furthermore there's unlikely to be much + * performance difference between them, so it doesn't seem worth + * costing out both ways if we get a hit on the first one. NULLS + * FIRST is more likely to be available if the operator is a + * reverse-sort operator, so try that first if reverse. + */ + if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse)) + continue; + if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, !reverse)) + continue; - if (!build_minmax_path(root, rel, info)) - return NULL; - total_cost += info->pathcost; + /* No indexable path for this aggregate, so fail */ + return; } /* - * Make the cost comparison. + * We're done until path generation is complete. Save info for later. + * (Setting root->minmax_aggs non-NIL signals we succeeded in making index + * access paths for all the aggregates.) + */ + root->minmax_aggs = aggs_list; +} + +/* + * optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes + * + * Check to see whether using the aggregate indexscans is cheaper than the + * generic aggregate method. If so, generate and return a Plan that does it + * that way. Otherwise, return NULL. + * + * Note: it seems likely that the generic method will never be cheaper + * in practice, except maybe for tiny tables where it'd hardly matter. + * Should we skip even trying to build the standard plan, if + * preprocess_minmax_aggregates succeeds? + * + * We are passed the preprocessed tlist, as well as the estimated costs for + * doing the aggregates the regular way, and the best path devised for + * computing the input of a standard Agg node. + */ +Plan * +optimize_minmax_aggregates(PlannerInfo *root, List *tlist, + const AggClauseCosts *aggcosts, Path *best_path) +{ + Query *parse = root->parse; + Cost total_cost; + Path agg_p; + Plan *plan; + Node *hqual; + QualCost tlist_cost; + ListCell *lc; + + /* Nothing to do if preprocess_minmax_aggs rejected the query */ + if (root->minmax_aggs == NIL) + return NULL; + + /* + * Now we have enough info to compare costs against the generic aggregate + * implementation. * * Note that we don't include evaluation cost of the tlist here; this is * OK since it isn't included in best_path's cost either, and should be * the same in either case. */ - cost_agg(&agg_p, root, AGG_PLAIN, list_length(aggs_list), + total_cost = 0; + foreach(lc, root->minmax_aggs) + { + MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); + + total_cost += mminfo->pathcost; + } + + cost_agg(&agg_p, root, AGG_PLAIN, aggcosts, 0, 0, best_path->startup_cost, best_path->total_cost, best_path->parent->rows); @@ -173,21 +233,21 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) /* * OK, we are going to generate an optimized plan. + * + * First, generate a subplan and output Param node for each agg. */ - - /* Pass 3: generate subplans and output Param nodes */ - foreach(l, aggs_list) + foreach(lc, root->minmax_aggs) { - make_agg_subplan(root, (MinMaxAggInfo *) lfirst(l)); + MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); + + make_agg_subplan(root, mminfo); } /* * Modify the targetlist and HAVING qual to reference subquery outputs */ - tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, - &aggs_list); - hqual = replace_aggs_with_params_mutator(parse->havingQual, - &aggs_list); + tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, root); + hqual = replace_aggs_with_params_mutator(parse->havingQual, root); /* * We have to replace Aggrefs with Params in equivalence classes too, else @@ -199,7 +259,7 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) */ mutate_eclass_expressions(root, replace_aggs_with_params_mutator, - &aggs_list); + (void *) root); /* * Generate the output plan --- basically just a Result @@ -241,36 +301,46 @@ find_minmax_aggs_walker(Node *node, List **context) Aggref *aggref = (Aggref *) node; Oid aggsortop; TargetEntry *curTarget; - MinMaxAggInfo *info; + MinMaxAggInfo *mminfo; ListCell *l; Assert(aggref->agglevelsup == 0); if (list_length(aggref->args) != 1 || aggref->aggorder != NIL) return true; /* it couldn't be MIN/MAX */ /* note: we do not care if DISTINCT is mentioned ... */ + curTarget = (TargetEntry *) linitial(aggref->args); aggsortop = fetch_agg_sort_op(aggref->aggfnoid); if (!OidIsValid(aggsortop)) return true; /* not a MIN/MAX aggregate */ + if (contain_mutable_functions((Node *) curTarget->expr)) + return true; /* not potentially indexable */ + + if (type_is_rowtype(exprType((Node *) curTarget->expr))) + return true; /* IS NOT NULL would have weird semantics */ + /* * Check whether it's already in the list, and add it if not. */ - curTarget = (TargetEntry *) linitial(aggref->args); foreach(l, *context) { - info = (MinMaxAggInfo *) lfirst(l); - if (info->aggfnoid == aggref->aggfnoid && - equal(info->target, curTarget->expr)) + mminfo = (MinMaxAggInfo *) lfirst(l); + if (mminfo->aggfnoid == aggref->aggfnoid && + equal(mminfo->target, curTarget->expr)) return false; } - info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo)); - info->aggfnoid = aggref->aggfnoid; - info->aggsortop = aggsortop; - info->target = curTarget->expr; + mminfo = makeNode(MinMaxAggInfo); + mminfo->aggfnoid = aggref->aggfnoid; + mminfo->aggsortop = aggsortop; + mminfo->target = curTarget->expr; + mminfo->subroot = NULL; /* don't compute path yet */ + mminfo->path = NULL; + mminfo->pathcost = 0; + mminfo->param = NULL; - *context = lappend(*context, info); + *context = lappend(*context, mminfo); /* * We need not recurse into the argument, since it can't contain any @@ -285,272 +355,164 @@ find_minmax_aggs_walker(Node *node, List **context) /* * build_minmax_path - * Given a MIN/MAX aggregate, try to find an index it can be optimized - * with. Build a Path describing the best such index path. + * Given a MIN/MAX aggregate, try to build an indexscan Path it can be + * optimized with. * - * Returns TRUE if successful, FALSE if not. In the TRUE case, info->path - * is filled in. - * - * XXX look at sharing more code with indxpath.c. - * - * Note: check_partial_indexes() must have been run previously. + * If successful, stash the best path in *mminfo and return TRUE. + * Otherwise, return FALSE. */ static bool -build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info) +build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, + Oid eqop, Oid sortop, bool nulls_first) { - IndexPath *best_path = NULL; - Cost best_cost = 0; - bool best_nulls_first = false; + PlannerInfo *subroot; + Query *parse; + TargetEntry *tle; NullTest *ntest; - List *allquals; - ListCell *l; + SortGroupClause *sortcl; + Path *cheapest_path; + Path *sorted_path; + double dNumGroups; + Cost path_cost; + double path_fraction; + + /*---------- + * Generate modified query of the form + * (SELECT col FROM tab + * WHERE col IS NOT NULL AND existing-quals + * ORDER BY col ASC/DESC + * LIMIT 1) + *---------- + */ + subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo)); + memcpy(subroot, root, sizeof(PlannerInfo)); + subroot->parse = parse = (Query *) copyObject(root->parse); + /* make sure subroot planning won't change root->init_plans contents */ + subroot->init_plans = list_copy(root->init_plans); + /* There shouldn't be any OJ info to translate, as yet */ + Assert(subroot->join_info_list == NIL); + /* and we haven't created PlaceHolderInfos, either */ + Assert(subroot->placeholder_list == NIL); - /* Build "target IS NOT NULL" expression for use below */ + /* single tlist entry that is the aggregate target */ + tle = makeTargetEntry(copyObject(mminfo->target), + (AttrNumber) 1, + pstrdup("agg_target"), + false); + parse->targetList = list_make1(tle); + + /* No HAVING, no DISTINCT, no aggregates anymore */ + parse->havingQual = NULL; + subroot->hasHavingQual = false; + parse->distinctClause = NIL; + parse->hasDistinctOn = false; + parse->hasAggs = false; + + /* Build "target IS NOT NULL" expression */ ntest = makeNode(NullTest); ntest->nulltesttype = IS_NOT_NULL; - ntest->arg = copyObject(info->target); - ntest->argisrow = type_is_rowtype(exprType((Node *) ntest->arg)); - if (ntest->argisrow) - return false; /* punt on composites */ - info->notnulltest = ntest; + ntest->arg = copyObject(mminfo->target); + /* we checked it wasn't a rowtype in find_minmax_aggs_walker */ + ntest->argisrow = false; - /* - * Build list of existing restriction clauses plus the notnull test. We - * cheat a bit by not bothering with a RestrictInfo node for the notnull - * test --- predicate_implied_by() won't care. - */ - allquals = list_concat(list_make1(ntest), rel->baserestrictinfo); + /* User might have had that in WHERE already */ + if (!list_member((List *) parse->jointree->quals, ntest)) + parse->jointree->quals = (Node *) + lcons(ntest, (List *) parse->jointree->quals); - foreach(l, rel->indexlist) - { - IndexOptInfo *index = (IndexOptInfo *) lfirst(l); - ScanDirection indexscandir = NoMovementScanDirection; - int indexcol; - int prevcol; - List *restrictclauses; - IndexPath *new_path; - Cost new_cost; - bool found_clause; - - /* Ignore non-btree indexes */ - if (index->relam != BTREE_AM_OID) - continue; + /* Build suitable ORDER BY clause */ + sortcl = makeNode(SortGroupClause); + sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList); + sortcl->eqop = eqop; + sortcl->sortop = sortop; + sortcl->nulls_first = nulls_first; + sortcl->hashable = false; /* no need to make this accurate */ + parse->sortClause = list_make1(sortcl); + + /* set up expressions for LIMIT 1 */ + parse->limitOffset = NULL; + parse->limitCount = (Node *) makeConst(INT8OID, -1, InvalidOid, + sizeof(int64), + Int64GetDatum(1), false, + FLOAT8PASSBYVAL); - /* - * Ignore partial indexes that do not match the query --- unless their - * predicates can be proven from the baserestrict list plus the IS NOT - * NULL test. In that case we can use them. - */ - if (index->indpred != NIL && !index->predOK && - !predicate_implied_by(index->indpred, allquals)) - continue; + /* + * Set up requested pathkeys. + */ + subroot->group_pathkeys = NIL; + subroot->window_pathkeys = NIL; + subroot->distinct_pathkeys = NIL; - /* - * Look for a match to one of the index columns. (In a stupidly - * designed index, there could be multiple matches, but we only care - * about the first one.) - */ - for (indexcol = 0; indexcol < index->ncolumns; indexcol++) - { - indexscandir = match_agg_to_index_col(info, index, indexcol); - if (!ScanDirectionIsNoMovement(indexscandir)) - break; - } - if (ScanDirectionIsNoMovement(indexscandir)) - continue; + subroot->sort_pathkeys = + make_pathkeys_for_sortclauses(subroot, + parse->sortClause, + parse->targetList, + false); - /* - * If the match is not at the first index column, we have to verify - * that there are "x = something" restrictions on all the earlier - * index columns. Since we'll need the restrictclauses list anyway to - * build the path, it's convenient to extract that first and then look - * through it for the equality restrictions. - */ - restrictclauses = group_clauses_by_indexkey(index, - index->rel->baserestrictinfo, - NIL, - NULL, - SAOP_FORBID, - &found_clause); - - if (list_length(restrictclauses) < indexcol) - continue; /* definitely haven't got enough */ - for (prevcol = 0; prevcol < indexcol; prevcol++) - { - List *rinfos = (List *) list_nth(restrictclauses, prevcol); - ListCell *ll; - - foreach(ll, rinfos) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(ll); - int strategy; - - /* Could be an IS_NULL test, if so ignore */ - if (!is_opclause(rinfo->clause)) - continue; - strategy = - get_op_opfamily_strategy(((OpExpr *) rinfo->clause)->opno, - index->opfamily[prevcol]); - if (strategy == BTEqualStrategyNumber) - break; - } - if (ll == NULL) - break; /* none are Equal for this index col */ - } - if (prevcol < indexcol) - continue; /* didn't find all Equal clauses */ + subroot->query_pathkeys = subroot->sort_pathkeys; - /* - * Build the access path. We don't bother marking it with pathkeys. - */ - new_path = create_index_path(root, index, - restrictclauses, - NIL, - indexscandir, - NULL); + /* + * Generate the best paths for this query, telling query_planner that we + * have LIMIT 1. + */ + query_planner(subroot, parse->targetList, 1.0, 1.0, + &cheapest_path, &sorted_path, &dNumGroups); - /* - * Estimate actual cost of fetching just one row. - */ - if (new_path->rows > 1.0) - new_cost = new_path->path.startup_cost + - (new_path->path.total_cost - new_path->path.startup_cost) - * 1.0 / new_path->rows; + /* + * Fail if no presorted path. However, if query_planner determines that + * the presorted path is also the cheapest, it will set sorted_path to + * NULL ... don't be fooled. (This is kind of a pain here, but it + * simplifies life for grouping_planner, so leave it be.) + */ + if (!sorted_path) + { + if (cheapest_path && + pathkeys_contained_in(subroot->sort_pathkeys, + cheapest_path->pathkeys)) + sorted_path = cheapest_path; else - new_cost = new_path->path.total_cost; - - /* - * Keep if first or if cheaper than previous best. - */ - if (best_path == NULL || new_cost < best_cost) - { - best_path = new_path; - best_cost = new_cost; - if (ScanDirectionIsForward(indexscandir)) - best_nulls_first = index->nulls_first[indexcol]; - else - best_nulls_first = !index->nulls_first[indexcol]; - } + return false; } - info->path = best_path; - info->pathcost = best_cost; - info->nulls_first = best_nulls_first; - return (best_path != NULL); -} - -/* - * match_agg_to_index_col - * Does an aggregate match an index column? - * - * It matches if its argument is equal to the index column's data and its - * sortop is either the forward or reverse sort operator for the column. - * - * We return ForwardScanDirection if match the forward sort operator, - * BackwardScanDirection if match the reverse sort operator, - * and NoMovementScanDirection if there's no match. - */ -static ScanDirection -match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol) -{ - ScanDirection result; - - /* Check for operator match first (cheaper) */ - if (info->aggsortop == index->fwdsortop[indexcol]) - result = ForwardScanDirection; - else if (info->aggsortop == index->revsortop[indexcol]) - result = BackwardScanDirection; + /* + * Determine cost to get just the first row of the presorted path. + * + * Note: cost calculation here should match + * compare_fractional_path_costs(). + */ + if (sorted_path->parent->rows > 1.0) + path_fraction = 1.0 / sorted_path->parent->rows; else - return NoMovementScanDirection; + path_fraction = 1.0; + + path_cost = sorted_path->startup_cost + + path_fraction * (sorted_path->total_cost - sorted_path->startup_cost); - /* Check for data match */ - if (!match_index_to_operand((Node *) info->target, indexcol, index)) - return NoMovementScanDirection; + /* Save state for further processing */ + mminfo->subroot = subroot; + mminfo->path = sorted_path; + mminfo->pathcost = path_cost; - return result; + return true; } /* * Construct a suitable plan for a converted aggregate query */ static void -make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) +make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo) { - PlannerInfo subroot; - Query *subparse; + PlannerInfo *subroot = mminfo->subroot; + Query *subparse = subroot->parse; Plan *plan; - IndexScan *iplan; - TargetEntry *tle; - SortGroupClause *sortcl; /* - * Generate a suitably modified query. Much of the work here is probably - * unnecessary in the normal case, but we want to make it look good if - * someone tries to EXPLAIN the result. + * Generate the plan for the subquery. We already have a Path, but we have + * to convert it to a Plan and attach a LIMIT node above it. */ - memcpy(&subroot, root, sizeof(PlannerInfo)); - subroot.parse = subparse = (Query *) copyObject(root->parse); - subparse->commandType = CMD_SELECT; - subparse->resultRelation = 0; - subparse->returningList = NIL; - subparse->utilityStmt = NULL; - subparse->intoClause = NULL; - subparse->hasAggs = false; - subparse->hasDistinctOn = false; - subparse->groupClause = NIL; - subparse->havingQual = NULL; - subparse->distinctClause = NIL; - subroot.hasHavingQual = false; + plan = create_plan(subroot, mminfo->path); - /* single tlist entry that is the aggregate target */ - tle = makeTargetEntry(copyObject(info->target), - 1, - pstrdup("agg_target"), - false); - subparse->targetList = list_make1(tle); - - /* set up the appropriate ORDER BY entry */ - sortcl = makeNode(SortGroupClause); - sortcl->tleSortGroupRef = assignSortGroupRef(tle, subparse->targetList); - sortcl->eqop = get_equality_op_for_ordering_op(info->aggsortop, NULL); - if (!OidIsValid(sortcl->eqop)) /* shouldn't happen */ - elog(ERROR, "could not find equality operator for ordering operator %u", - info->aggsortop); - sortcl->sortop = info->aggsortop; - sortcl->nulls_first = info->nulls_first; - subparse->sortClause = list_make1(sortcl); - - /* set up LIMIT 1 */ - subparse->limitOffset = NULL; - subparse->limitCount = (Node *) makeConst(INT8OID, -1, sizeof(int64), - Int64GetDatum(1), false, - FLOAT8PASSBYVAL); - - /* - * Generate the plan for the subquery. We already have a Path for the - * basic indexscan, but we have to convert it to a Plan and attach a LIMIT - * node above it. - * - * Also we must add a "WHERE target IS NOT NULL" restriction to the - * indexscan, to be sure we don't return a NULL, which'd be contrary to - * the standard behavior of MIN/MAX. - * - * The NOT NULL qual has to go on the actual indexscan; create_plan might - * have stuck a gating Result atop that, if there were any pseudoconstant - * quals. - */ - plan = create_plan(&subroot, (Path *) info->path); - - plan->targetlist = copyObject(subparse->targetList); - - if (IsA(plan, Result)) - iplan = (IndexScan *) plan->lefttree; - else - iplan = (IndexScan *) plan; - if (!IsA(iplan, IndexScan)) - elog(ERROR, "result of create_plan(IndexPath) isn't an IndexScan"); - - attach_notnull_index_qual(info, iplan); + plan->targetlist = subparse->targetList; plan = (Plan *) make_limit(plan, subparse->limitOffset, @@ -560,184 +522,28 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) /* * Convert the plan into an InitPlan, and make a Param for its result. */ - info->param = SS_make_initplan_from_plan(&subroot, plan, - exprType((Node *) tle->expr), - -1); - - /* - * Put the updated list of InitPlans back into the outer PlannerInfo. - */ - root->init_plans = subroot.init_plans; -} - -/* - * Add "target IS NOT NULL" to the quals of the given indexscan. - * - * This is trickier than it sounds because the new qual has to be added at an - * appropriate place in the qual list, to preserve the list's ordering by - * index column position. - */ -static void -attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan) -{ - NullTest *ntest; - List *newindexqual; - List *newindexqualorig; - bool done; - ListCell *lc1; - ListCell *lc2; - Expr *leftop; - AttrNumber targetattno; - - /* - * We can skip adding the NOT NULL qual if it duplicates either an - * already-given WHERE condition, or a clause of the index predicate. - */ - if (list_member(iplan->indexqualorig, info->notnulltest) || - list_member(info->path->indexinfo->indpred, info->notnulltest)) - return; - - /* Need a "fixed" copy as well as the original */ - ntest = copyObject(info->notnulltest); - ntest->arg = (Expr *) fix_indexqual_operand((Node *) ntest->arg, - info->path->indexinfo); - - /* Identify the target index column from the "fixed" copy */ - leftop = ntest->arg; - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!IsA(leftop, Var)) - elog(ERROR, "NullTest indexqual has wrong key"); - - targetattno = ((Var *) leftop)->varattno; + mminfo->param = + SS_make_initplan_from_plan(subroot, plan, + exprType((Node *) mminfo->target), + -1, + exprCollation((Node *) mminfo->target)); /* - * list.c doesn't expose a primitive to insert a list cell at an arbitrary - * position, so our strategy is to copy the lists and insert the null test - * when we reach an appropriate spot. + * Make sure the initplan gets into the outer PlannerInfo, along with any + * other initplans generated by the sub-planning run. We had to include + * the outer PlannerInfo's pre-existing initplans into the inner one's + * init_plans list earlier, so make sure we don't put back any duplicate + * entries. */ - newindexqual = newindexqualorig = NIL; - done = false; - - forboth(lc1, iplan->indexqual, lc2, iplan->indexqualorig) - { - Expr *qual = (Expr *) lfirst(lc1); - Expr *qualorig = (Expr *) lfirst(lc2); - AttrNumber varattno; - - /* - * Identify which index column this qual is for. This code should - * match the qual disassembly code in ExecIndexBuildScanKeys. - */ - if (IsA(qual, OpExpr)) - { - /* indexkey op expression */ - leftop = (Expr *) get_leftop(qual); - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!IsA(leftop, Var)) - elog(ERROR, "indexqual doesn't have key on left side"); - - varattno = ((Var *) leftop)->varattno; - } - else if (IsA(qual, RowCompareExpr)) - { - /* (indexkey, indexkey, ...) op (expression, expression, ...) */ - RowCompareExpr *rc = (RowCompareExpr *) qual; - - /* - * Examine just the first column of the rowcompare, which is what - * determines its placement in the overall qual list. - */ - leftop = (Expr *) linitial(rc->largs); - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!IsA(leftop, Var)) - elog(ERROR, "indexqual doesn't have key on left side"); - - varattno = ((Var *) leftop)->varattno; - } - else if (IsA(qual, ScalarArrayOpExpr)) - { - /* indexkey op ANY (array-expression) */ - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual; - - leftop = (Expr *) linitial(saop->args); - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!IsA(leftop, Var)) - elog(ERROR, "indexqual doesn't have key on left side"); - - varattno = ((Var *) leftop)->varattno; - } - else if (IsA(qual, NullTest)) - { - /* indexkey IS NULL or indexkey IS NOT NULL */ - NullTest *ntest = (NullTest *) qual; - - leftop = ntest->arg; - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!IsA(leftop, Var)) - elog(ERROR, "NullTest indexqual has wrong key"); - - varattno = ((Var *) leftop)->varattno; - } - else - { - elog(ERROR, "unsupported indexqual type: %d", - (int) nodeTag(qual)); - varattno = 0; /* keep compiler quiet */ - } - - /* Insert the null test at the first place it can legally go */ - if (!done && targetattno <= varattno) - { - newindexqual = lappend(newindexqual, ntest); - newindexqualorig = lappend(newindexqualorig, info->notnulltest); - done = true; - } - - newindexqual = lappend(newindexqual, qual); - newindexqualorig = lappend(newindexqualorig, qualorig); - } - - /* Add the null test at the end if it must follow all existing quals */ - if (!done) - { - newindexqual = lappend(newindexqual, ntest); - newindexqualorig = lappend(newindexqualorig, info->notnulltest); - } - - iplan->indexqual = newindexqual; - iplan->indexqualorig = newindexqualorig; + root->init_plans = list_concat_unique_ptr(root->init_plans, + subroot->init_plans); } /* * Replace original aggregate calls with subplan output Params */ static Node * -replace_aggs_with_params_mutator(Node *node, List **context) +replace_aggs_with_params_mutator(Node *node, PlannerInfo *root) { if (node == NULL) return NULL; @@ -745,21 +551,21 @@ replace_aggs_with_params_mutator(Node *node, List **context) { Aggref *aggref = (Aggref *) node; TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args); - ListCell *l; + ListCell *lc; - foreach(l, *context) + foreach(lc, root->minmax_aggs) { - MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l); + MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); - if (info->aggfnoid == aggref->aggfnoid && - equal(info->target, curTarget->expr)) - return (Node *) info->param; + if (mminfo->aggfnoid == aggref->aggfnoid && + equal(mminfo->target, curTarget->expr)) + return (Node *) mminfo->param; } - elog(ERROR, "failed to re-find aggregate info record"); + elog(ERROR, "failed to re-find MinMaxAggInfo record"); } Assert(!IsA(node, SubLink)); return expression_tree_mutator(node, replace_aggs_with_params_mutator, - (void *) context); + (void *) root); } /* diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index ad6c6f5858..ff39d5754d 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -9,17 +9,18 @@ * shorn of features like subselects, inheritance, aggregates, grouping, * and so on. (Those are the things planner.c deals with.) * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.119 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/plan/planmain.c * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "miscadmin.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" @@ -29,6 +30,10 @@ #include "utils/selfuncs.h" +/* Local functions */ +static void canonicalize_all_pathkeys(PlannerInfo *root); + + /* * query_planner * Generate a path (that is, a simplified plan) for a basic query, @@ -67,9 +72,9 @@ * PlannerInfo field and not a passed parameter is that the low-level routines * in indxpath.c need to see it.) * - * Note: the PlannerInfo node also includes group_pathkeys, window_pathkeys, - * distinct_pathkeys, and sort_pathkeys, which like query_pathkeys need to be - * canonicalized once the info is available. + * Note: the PlannerInfo node includes other pathkeys fields besides + * query_pathkeys, all of which need to be canonicalized once the info is + * available. See canonicalize_all_pathkeys. * * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -96,8 +101,9 @@ query_planner(PlannerInfo *root, List *tlist, ListCell *lc; double total_pages; - /* Make tuple_fraction accessible to lower-level routines */ + /* Make tuple_fraction, limit_tuples accessible to lower-level routines */ root->tuple_fraction = tuple_fraction; + root->limit_tuples = limit_tuples; *num_groups = 1; /* default result */ @@ -117,16 +123,7 @@ query_planner(PlannerInfo *root, List *tlist, * something like "SELECT 2+2 ORDER BY 1". */ root->canon_pathkeys = NIL; - root->query_pathkeys = canonicalize_pathkeys(root, - root->query_pathkeys); - root->group_pathkeys = canonicalize_pathkeys(root, - root->group_pathkeys); - root->window_pathkeys = canonicalize_pathkeys(root, - root->window_pathkeys); - root->distinct_pathkeys = canonicalize_pathkeys(root, - root->distinct_pathkeys); - root->sort_pathkeys = canonicalize_pathkeys(root, - root->sort_pathkeys); + canonicalize_all_pathkeys(root); return; } @@ -135,7 +132,7 @@ query_planner(PlannerInfo *root, List *tlist, * for "simple" rels. * * NOTE: append_rel_list was set up by subquery_planner, so do not touch - * here; eq_classes may contain data already, too. + * here; eq_classes and minmax_aggs may contain data already, too. */ root->simple_rel_array_size = list_length(parse->rtable) + 1; root->simple_rel_array = (RelOptInfo **) @@ -180,14 +177,19 @@ query_planner(PlannerInfo *root, List *tlist, add_base_rels_to_query(root, (Node *) parse->jointree); /* - * Examine the targetlist and qualifications, adding entries to baserel - * targetlists for all referenced Vars. Restrict and join clauses are - * added to appropriate lists belonging to the mentioned relations. We - * also build EquivalenceClasses for provably equivalent expressions, and - * form a target joinlist for make_one_rel() to work from. + * Examine the targetlist and join tree, adding entries to baserel + * targetlists for all referenced Vars, and generating PlaceHolderInfo + * entries for all referenced PlaceHolderVars. Restrict and join clauses + * are added to appropriate lists belonging to the mentioned relations. We + * also build EquivalenceClasses for provably equivalent expressions. The + * SpecialJoinInfo list is also built to hold information about join order + * restrictions. Finally, we form a target joinlist for make_one_rel() to + * work from. */ build_base_rel_tlists(root, tlist); + find_placeholders_in_jointree(root); + joinlist = deconstruct_jointree(root); /* @@ -206,22 +208,19 @@ query_planner(PlannerInfo *root, List *tlist, /* * We have completed merging equivalence sets, so it's now possible to - * convert the requested query_pathkeys to canonical form. Also - * canonicalize the groupClause, windowClause, distinctClause and - * sortClause pathkeys for use later. + * convert previously generated pathkeys (in particular, the requested + * query_pathkeys) to canonical form. */ - root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys); - root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys); - root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys); - root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys); - root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys); + canonicalize_all_pathkeys(root); /* * Examine any "placeholder" expressions generated during subquery pullup. - * Make sure that we know what level to evaluate them at, and that the - * Vars they need are marked as needed. + * Make sure that the Vars they need are marked as needed at the relevant + * join level. This must be done before join removal because it might + * cause Vars or placeholders to be needed above a join when they weren't + * so marked before. */ - fix_placeholder_eval_levels(root); + fix_placeholder_input_needed_levels(root); /* * Remove any useless outer joins. Ideally this would be done during @@ -317,6 +316,9 @@ query_planner(PlannerInfo *root, List *tlist, !pathkeys_contained_in(root->distinct_pathkeys, root->group_pathkeys) || !pathkeys_contained_in(root->window_pathkeys, root->group_pathkeys)) tuple_fraction = 0.0; + + /* In any case, limit_tuples shouldn't be specified here */ + Assert(limit_tuples < 0); } else if (parse->hasAggs || root->hasHavingQual) { @@ -325,6 +327,9 @@ query_planner(PlannerInfo *root, List *tlist, * it will deliver a single result row (so leave *num_groups 1). */ tuple_fraction = 0.0; + + /* limit_tuples shouldn't be specified here */ + Assert(limit_tuples < 0); } else if (parse->distinctClause) { @@ -349,6 +354,9 @@ query_planner(PlannerInfo *root, List *tlist, */ if (tuple_fraction >= 1.0) tuple_fraction /= *num_groups; + + /* limit_tuples shouldn't be specified here */ + Assert(limit_tuples < 0); } else { @@ -408,7 +416,7 @@ query_planner(PlannerInfo *root, List *tlist, cost_sort(&sort_path, root, root->query_pathkeys, cheapestpath->total_cost, final_rel->rows, final_rel->width, - limit_tuples); + 0.0, work_mem, limit_tuples); } if (compare_fractional_path_costs(sortedpath, &sort_path, @@ -422,3 +430,19 @@ query_planner(PlannerInfo *root, List *tlist, *cheapest_path = cheapestpath; *sorted_path = sortedpath; } + + +/* + * canonicalize_all_pathkeys + * Canonicalize all pathkeys that were generated before entering + * query_planner and then stashed in PlannerInfo. + */ +static void +canonicalize_all_pathkeys(PlannerInfo *root) +{ + root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys); + root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys); + root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys); + root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys); + root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys); +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5250905b59..326cc9cc70 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3,12 +3,12 @@ * planner.c * The query optimizer external interface. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.267 2010/03/30 21:58:10 tgl Exp $ + * src/backend/optimizer/plan/planner.c * *------------------------------------------------------------------------- */ @@ -26,6 +26,7 @@ #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" @@ -77,7 +78,7 @@ static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, Path *cheapest_path, Path *sorted_path, - double dNumGroups, AggClauseCounts *agg_counts); + double dNumGroups, AggClauseCosts *agg_costs); static bool choose_hashed_distinct(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, @@ -175,9 +176,11 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob->rewindPlanIDs = NULL; glob->finalrtable = NIL; glob->finalrowmarks = NIL; + glob->resultRelations = NIL; glob->relationOids = NIL; glob->invalItems = NIL; glob->lastPHId = 0; + glob->lastRowMarkId = 0; glob->transientPlan = false; /* Determine what fraction of the plan is likely to be scanned */ @@ -225,6 +228,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) /* final cleanup of the plan */ Assert(glob->finalrtable == NIL); Assert(glob->finalrowmarks == NIL); + Assert(glob->resultRelations == NIL); top_plan = set_plan_references(glob, top_plan, root->parse->rtable, root->rowMarks); @@ -271,11 +275,12 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->commandType = parse->commandType; result->hasReturning = (parse->returningList != NIL); + result->hasModifyingCTE = parse->hasModifyingCTE; result->canSetTag = parse->canSetTag; result->transientPlan = glob->transientPlan; result->planTree = top_plan; result->rtable = glob->finalrtable; - result->resultRelations = root->resultRelations; + result->resultRelations = glob->resultRelations; result->utilityStmt = parse->utilityStmt; result->intoClause = parse->intoClause; result->subplans = glob->subplans; @@ -377,13 +382,22 @@ subquery_planner(PlannerGlobal *glob, Query *parse, inline_set_returning_functions(root); /* - * Check to see if any subqueries in the rangetable can be merged into - * this query. + * Check to see if any subqueries in the jointree can be merged into this + * query. */ parse->jointree = (FromExpr *) pull_up_subqueries(root, (Node *) parse->jointree, NULL, NULL); /* + * If this is a simple UNION ALL query, flatten it into an appendrel. We + * do this now because it requires applying pull_up_subqueries to the leaf + * queries of the UNION ALL, which weren't touched above because they + * weren't referenced by the jointree (they will be after we do this). + */ + if (parse->setOperations) + flatten_simple_union_all(root); + + /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can * avoid the expense of doing flatten_join_alias_vars(). Also check for * outer joins --- if none, we can skip reduce_outer_joins(). This must be @@ -597,7 +611,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rowMarks = root->rowMarks; plan = (Plan *) make_modifytable(parse->commandType, - copyObject(root->resultRelations), + parse->canSetTag, + list_make1_int(parse->resultRelation), list_make1(plan), returningLists, rowMarks, @@ -813,7 +828,7 @@ inheritance_planner(PlannerInfo *root) /* Make sure any initplans from this rel get into the outer list */ root->init_plans = list_concat(root->init_plans, subroot.init_plans); - /* Build target-relations list for the executor */ + /* Build list of target-relation RT indexes */ resultRelations = lappend_int(resultRelations, appinfo->child_relid); /* Build list of per-relation RETURNING targetlists */ @@ -829,8 +844,6 @@ inheritance_planner(PlannerInfo *root) } } - root->resultRelations = resultRelations; - /* Mark result as unordered (probably unnecessary) */ root->query_pathkeys = NIL; @@ -840,7 +853,6 @@ inheritance_planner(PlannerInfo *root) */ if (subplans == NIL) { - root->resultRelations = list_make1_int(parentRTindex); /* although dummy, it must have a valid tlist for executor */ tlist = preprocess_targetlist(root, parse->targetList); return (Plan *) make_result(root, @@ -875,7 +887,8 @@ inheritance_planner(PlannerInfo *root) /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */ return (Plan *) make_modifytable(parse->commandType, - copyObject(root->resultRelations), + parse->canSetTag, + resultRelations, subplans, returningLists, rowMarks, @@ -995,6 +1008,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) { /* No set operations, do regular planning */ List *sub_tlist; + double sub_limit_tuples; AttrNumber *groupColIdx = NULL; bool need_tlist_eval = true; QualCost tlist_cost; @@ -1002,7 +1016,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) Path *sorted_path; Path *best_path; long numGroups = 0; - AggClauseCounts agg_counts; + AggClauseCosts agg_costs; int numGroupCols; double path_rows; int path_width; @@ -1010,7 +1024,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) WindowFuncLists *wflists = NULL; List *activeWindows = NIL; - MemSet(&agg_counts, 0, sizeof(AggClauseCounts)); + MemSet(&agg_costs, 0, sizeof(AggClauseCosts)); /* A recursive query should always have setOperations */ Assert(!root->hasRecursion); @@ -1047,6 +1061,33 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) &groupColIdx, &need_tlist_eval); /* + * Do aggregate preprocessing, if the query has any aggs. + * + * Note: think not that we can turn off hasAggs if we find no aggs. It + * is possible for constant-expression simplification to remove all + * explicit references to aggs, but we still have to follow the + * aggregate semantics (eg, producing only one output row). + */ + if (parse->hasAggs) + { + /* + * Collect statistics about aggregates for estimating costs. Note: + * we do not attempt to detect duplicate aggregates here; a + * somewhat-overestimated cost is okay for our present purposes. + */ + count_agg_clauses(root, (Node *) tlist, &agg_costs); + count_agg_clauses(root, parse->havingQual, &agg_costs); + + /* + * Preprocess MIN/MAX aggregates, if any. Note: be careful about + * adding logic between here and the optimize_minmax_aggregates + * call. Anything that is needed in MIN/MAX-optimizable cases + * will have to be duplicated in planagg.c. + */ + preprocess_minmax_aggregates(root, tlist); + } + + /* * Calculate pathkeys that represent grouping/ordering requirements. * Stash them in PlannerInfo so that query_planner can canonicalize * them after EquivalenceClasses have been formed. The sortClause is @@ -1093,23 +1134,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) false); /* - * Will need actual number of aggregates for estimating costs. - * - * Note: we do not attempt to detect duplicate aggregates here; a - * somewhat-overestimated count is okay for our present purposes. - * - * Note: think not that we can turn off hasAggs if we find no aggs. It - * is possible for constant-expression simplification to remove all - * explicit references to aggs, but we still have to follow the - * aggregate semantics (eg, producing only one output row). - */ - if (parse->hasAggs) - { - count_agg_clauses((Node *) tlist, &agg_counts); - count_agg_clauses(parse->havingQual, &agg_counts); - } - - /* * Figure out whether we want a sorted result from query_planner. * * If we have a sortable GROUP BY clause, then we want a result sorted @@ -1140,12 +1164,27 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) root->query_pathkeys = NIL; /* + * Figure out whether there's a hard limit on the number of rows that + * query_planner's result subplan needs to return. Even if we know a + * hard limit overall, it doesn't apply if the query has any + * grouping/aggregation operations. + */ + if (parse->groupClause || + parse->distinctClause || + parse->hasAggs || + parse->hasWindowFuncs || + root->hasHavingQual) + sub_limit_tuples = -1.0; + else + sub_limit_tuples = limit_tuples; + + /* * Generate the best unsorted and presorted paths for this Query (but * note there may not be any presorted path). query_planner will also * estimate the number of groups in the query, and canonicalize all * the pathkeys. */ - query_planner(root, sub_tlist, tuple_fraction, limit_tuples, + query_planner(root, sub_tlist, tuple_fraction, sub_limit_tuples, &cheapest_path, &sorted_path, &dNumGroups); /* @@ -1174,7 +1213,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tuple_fraction, limit_tuples, path_rows, path_width, cheapest_path, sorted_path, - dNumGroups, &agg_counts); + dNumGroups, &agg_costs); /* Also convert # groups to long int --- but 'ware overflow! */ numGroups = (long) Min(dNumGroups, (double) LONG_MAX); } @@ -1217,6 +1256,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) */ result_plan = optimize_minmax_aggregates(root, tlist, + &agg_costs, best_path); if (result_plan != NULL) { @@ -1328,11 +1368,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tlist, (List *) parse->havingQual, AGG_HASHED, + &agg_costs, numGroupCols, groupColIdx, extract_grouping_ops(parse->groupClause), numGroups, - agg_counts.numAggs, result_plan); /* Hashed aggregation produces randomly-ordered results */ current_pathkeys = NIL; @@ -1371,11 +1411,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tlist, (List *) parse->havingQual, aggstrategy, + &agg_costs, numGroupCols, groupColIdx, extract_grouping_ops(parse->groupClause), numGroups, - agg_counts.numAggs, result_plan); } else if (parse->groupClause) @@ -1567,7 +1607,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) result_plan = (Plan *) make_windowagg(root, (List *) copyObject(window_tlist), - list_length(wflists->windowFuncs[wc->winref]), + wflists->windowFuncs[wc->winref], wc->winref, partNumCols, partColIdx, @@ -1633,12 +1673,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) result_plan->targetlist, NIL, AGG_HASHED, + NULL, list_length(parse->distinctClause), extract_grouping_cols(parse->distinctClause, result_plan->targetlist), extract_grouping_ops(parse->distinctClause), numDistinctRows, - 0, result_plan); /* Hashed aggregation produces randomly-ordered results */ current_pathkeys = NIL; @@ -1738,12 +1778,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) count_est); } - /* Compute result-relations list if needed */ - if (parse->resultRelation) - root->resultRelations = list_make1_int(parse->resultRelation); - else - root->resultRelations = NIL; - /* * Return the actual output ordering in query_pathkeys for possible use by * an outer query level. @@ -1899,16 +1933,13 @@ preprocess_rowmarks(PlannerInfo *root) newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = rc->rti; + newrc->rowmarkId = ++(root->glob->lastRowMarkId); if (rc->forUpdate) newrc->markType = ROW_MARK_EXCLUSIVE; else newrc->markType = ROW_MARK_SHARE; newrc->noWait = rc->noWait; newrc->isParent = false; - /* attnos will be assigned in preprocess_targetlist */ - newrc->ctidAttNo = InvalidAttrNumber; - newrc->toidAttNo = InvalidAttrNumber; - newrc->wholeAttNo = InvalidAttrNumber; prowmarks = lappend(prowmarks, newrc); } @@ -1928,17 +1959,15 @@ preprocess_rowmarks(PlannerInfo *root) newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; + newrc->rowmarkId = ++(root->glob->lastRowMarkId); /* real tables support REFERENCE, anything else needs COPY */ - if (rte->rtekind == RTE_RELATION) + if (rte->rtekind == RTE_RELATION && + rte->relkind != RELKIND_FOREIGN_TABLE) newrc->markType = ROW_MARK_REFERENCE; else newrc->markType = ROW_MARK_COPY; newrc->noWait = false; /* doesn't matter */ newrc->isParent = false; - /* attnos will be assigned in preprocess_targetlist */ - newrc->ctidAttNo = InvalidAttrNumber; - newrc->toidAttNo = InvalidAttrNumber; - newrc->wholeAttNo = InvalidAttrNumber; prowmarks = lappend(prowmarks, newrc); } @@ -2232,7 +2261,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, Path *cheapest_path, Path *sorted_path, - double dNumGroups, AggClauseCounts *agg_counts) + double dNumGroups, AggClauseCosts *agg_costs) { Query *parse = root->parse; int numGroupCols = list_length(parse->groupClause); @@ -2250,7 +2279,7 @@ choose_hashed_grouping(PlannerInfo *root, * the hash table, and/or running many sorts in parallel, either of which * seems like a certain loser.) */ - can_hash = (agg_counts->numOrderedAggs == 0 && + can_hash = (agg_costs->numOrderedAggs == 0 && grouping_is_hashable(parse->groupClause)); can_sort = grouping_is_sortable(parse->groupClause); @@ -2280,9 +2309,9 @@ choose_hashed_grouping(PlannerInfo *root, /* Estimate per-hash-entry space at tuple width... */ hashentrysize = MAXALIGN(path_width) + MAXALIGN(sizeof(MinimalTupleData)); /* plus space for pass-by-ref transition values... */ - hashentrysize += agg_counts->transitionSpace; + hashentrysize += agg_costs->transitionSpace; /* plus the per-hash-entry overhead */ - hashentrysize += hash_agg_entry_size(agg_counts->numAggs); + hashentrysize += hash_agg_entry_size(agg_costs->numAggs); if (hashentrysize * dNumGroups > work_mem * 1024L) return false; @@ -2316,14 +2345,15 @@ choose_hashed_grouping(PlannerInfo *root, * These path variables are dummies that just hold cost fields; we don't * make actual Paths for these steps. */ - cost_agg(&hashed_p, root, AGG_HASHED, agg_counts->numAggs, + cost_agg(&hashed_p, root, AGG_HASHED, agg_costs, numGroupCols, dNumGroups, cheapest_path->startup_cost, cheapest_path->total_cost, path_rows); /* Result of hashed agg is always unsorted */ if (target_pathkeys) cost_sort(&hashed_p, root, target_pathkeys, hashed_p.total_cost, - dNumGroups, path_width, limit_tuples); + dNumGroups, path_width, + 0.0, work_mem, limit_tuples); if (sorted_path) { @@ -2340,12 +2370,13 @@ choose_hashed_grouping(PlannerInfo *root, if (!pathkeys_contained_in(root->group_pathkeys, current_pathkeys)) { cost_sort(&sorted_p, root, root->group_pathkeys, sorted_p.total_cost, - path_rows, path_width, -1.0); + path_rows, path_width, + 0.0, work_mem, -1.0); current_pathkeys = root->group_pathkeys; } if (parse->hasAggs) - cost_agg(&sorted_p, root, AGG_SORTED, agg_counts->numAggs, + cost_agg(&sorted_p, root, AGG_SORTED, agg_costs, numGroupCols, dNumGroups, sorted_p.startup_cost, sorted_p.total_cost, path_rows); @@ -2357,7 +2388,8 @@ choose_hashed_grouping(PlannerInfo *root, if (target_pathkeys && !pathkeys_contained_in(target_pathkeys, current_pathkeys)) cost_sort(&sorted_p, root, target_pathkeys, sorted_p.total_cost, - dNumGroups, path_width, limit_tuples); + dNumGroups, path_width, + 0.0, work_mem, limit_tuples); /* * Now make the decision using the top-level tuple fraction. First we @@ -2463,7 +2495,7 @@ choose_hashed_distinct(PlannerInfo *root, * These path variables are dummies that just hold cost fields; we don't * make actual Paths for these steps. */ - cost_agg(&hashed_p, root, AGG_HASHED, 0, + cost_agg(&hashed_p, root, AGG_HASHED, NULL, numDistinctCols, dNumDistinctRows, cheapest_startup_cost, cheapest_total_cost, path_rows); @@ -2474,7 +2506,8 @@ choose_hashed_distinct(PlannerInfo *root, */ if (parse->sortClause) cost_sort(&hashed_p, root, root->sort_pathkeys, hashed_p.total_cost, - dNumDistinctRows, path_width, limit_tuples); + dNumDistinctRows, path_width, + 0.0, work_mem, limit_tuples); /* * Now for the GROUP case. See comments in grouping_planner about the @@ -2497,7 +2530,8 @@ choose_hashed_distinct(PlannerInfo *root, else current_pathkeys = root->sort_pathkeys; cost_sort(&sorted_p, root, current_pathkeys, sorted_p.total_cost, - path_rows, path_width, -1.0); + path_rows, path_width, + 0.0, work_mem, -1.0); } cost_group(&sorted_p, root, numDistinctCols, dNumDistinctRows, sorted_p.startup_cost, sorted_p.total_cost, @@ -2505,7 +2539,8 @@ choose_hashed_distinct(PlannerInfo *root, if (parse->sortClause && !pathkeys_contained_in(root->sort_pathkeys, current_pathkeys)) cost_sort(&sorted_p, root, root->sort_pathkeys, sorted_p.total_cost, - dNumDistinctRows, path_width, limit_tuples); + dNumDistinctRows, path_width, + 0.0, work_mem, limit_tuples); /* * Now make the decision using the top-level tuple fraction. First we @@ -3044,3 +3079,115 @@ expression_planner(Expr *expr) return (Expr *) result; } + + +/* + * plan_cluster_use_sort + * Use the planner to decide how CLUSTER should implement sorting + * + * tableOid is the OID of a table to be clustered on its index indexOid + * (which is already known to be a btree index). Decide whether it's + * cheaper to do an indexscan or a seqscan-plus-sort to execute the CLUSTER. + * Return TRUE to use sorting, FALSE to use an indexscan. + * + * Note: caller had better already hold some type of lock on the table. + */ +bool +plan_cluster_use_sort(Oid tableOid, Oid indexOid) +{ + PlannerInfo *root; + Query *query; + PlannerGlobal *glob; + RangeTblEntry *rte; + RelOptInfo *rel; + IndexOptInfo *indexInfo; + QualCost indexExprCost; + Cost comparisonCost; + Path *seqScanPath; + Path seqScanAndSortPath; + IndexPath *indexScanPath; + ListCell *lc; + + /* Set up mostly-dummy planner state */ + query = makeNode(Query); + query->commandType = CMD_SELECT; + + glob = makeNode(PlannerGlobal); + + root = makeNode(PlannerInfo); + root->parse = query; + root->glob = glob; + root->query_level = 1; + root->planner_cxt = CurrentMemoryContext; + root->wt_param_id = -1; + + /* Build a minimal RTE for the rel */ + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RELATION; + rte->relid = tableOid; + rte->relkind = RELKIND_RELATION; + rte->inh = false; + rte->inFromCl = true; + query->rtable = list_make1(rte); + + /* ... and insert it into PlannerInfo */ + root->simple_rel_array_size = 2; + root->simple_rel_array = (RelOptInfo **) + palloc0(root->simple_rel_array_size * sizeof(RelOptInfo *)); + root->simple_rte_array = (RangeTblEntry **) + palloc0(root->simple_rel_array_size * sizeof(RangeTblEntry *)); + root->simple_rte_array[1] = rte; + + /* Build RelOptInfo */ + rel = build_simple_rel(root, 1, RELOPT_BASEREL); + + /* Locate IndexOptInfo for the target index */ + indexInfo = NULL; + foreach(lc, rel->indexlist) + { + indexInfo = (IndexOptInfo *) lfirst(lc); + if (indexInfo->indexoid == indexOid) + break; + } + + /* + * It's possible that get_relation_info did not generate an IndexOptInfo + * for the desired index; this could happen if it's not yet reached its + * indcheckxmin usability horizon, or if it's a system index and we're + * ignoring system indexes. In such cases we should tell CLUSTER to not + * trust the index contents but use seqscan-and-sort. + */ + if (lc == NULL) /* not in the list? */ + return true; /* use sort */ + + /* + * Rather than doing all the pushups that would be needed to use + * set_baserel_size_estimates, just do a quick hack for rows and width. + */ + rel->rows = rel->tuples; + rel->width = get_relation_data_width(tableOid, NULL); + + root->total_table_pages = rel->pages; + + /* + * Determine eval cost of the index expressions, if any. We need to + * charge twice that amount for each tuple comparison that happens during + * the sort, since tuplesort.c will have to re-evaluate the index + * expressions each time. (XXX that's pretty inefficient...) + */ + cost_qual_eval(&indexExprCost, indexInfo->indexprs, root); + comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple); + + /* Estimate the cost of seq scan + sort */ + seqScanPath = create_seqscan_path(root, rel); + cost_sort(&seqScanAndSortPath, root, NIL, + seqScanPath->total_cost, rel->tuples, rel->width, + comparisonCost, maintenance_work_mem, -1.0); + + /* Estimate the cost of index scan */ + indexScanPath = create_index_path(root, indexInfo, + NIL, NIL, NIL, + ForwardScanDirection, NULL); + + return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost); +} diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 826e1ecbe0..931f2adfc4 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -4,12 +4,12 @@ * Post-processing of a completed plan tree: fix references to subplan * vars, compute regproc values for operators, etc * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.160 2010/02/26 02:00:45 momjian Exp $ + * src/backend/optimizer/plan/setrefs.c * *------------------------------------------------------------------------- */ @@ -92,8 +92,6 @@ static Node *fix_scan_expr(PlannerGlobal *glob, Node *node, int rtoffset); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context); static void set_join_references(PlannerGlobal *glob, Join *join, int rtoffset); -static void set_inner_join_references(PlannerGlobal *glob, Plan *inner_plan, - indexed_tlist *outer_itlist); static void set_upper_references(PlannerGlobal *glob, Plan *plan, int rtoffset); static void set_dummy_tlist_references(Plan *plan, int rtoffset); static indexed_tlist *build_tlist_index(List *tlist); @@ -178,8 +176,9 @@ static bool extract_query_dependencies_walker(Node *node, * The return value is normally the same Plan node passed in, but can be * different when the passed-in Plan is a SubqueryScan we decide isn't needed. * - * The flattened rangetable entries are appended to glob->finalrtable, - * and we also append rowmarks entries to glob->finalrowmarks. + * The flattened rangetable entries are appended to glob->finalrtable. + * Also, rowmarks entries are appended to glob->finalrowmarks, and the + * RT indexes of ModifyTable result relations to glob->resultRelations. * Plan dependencies are appended to glob->relationOids (for relations) * and glob->invalItems (for everything else). * @@ -218,9 +217,12 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, newrte->funcexpr = NULL; newrte->funccoltypes = NIL; newrte->funccoltypmods = NIL; + newrte->funccolcollations = NIL; newrte->values_lists = NIL; + newrte->values_collations = NIL; newrte->ctecoltypes = NIL; newrte->ctecoltypmods = NIL; + newrte->ctecolcollations = NIL; glob->finalrtable = lappend(glob->finalrtable, newrte); @@ -255,7 +257,7 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, newrc = (PlanRowMark *) palloc(sizeof(PlanRowMark)); memcpy(newrc, rc, sizeof(PlanRowMark)); - /* adjust indexes */ + /* adjust indexes ... but *not* the rowmarkId */ newrc->rti += rtoffset; newrc->prti += rtoffset; @@ -306,6 +308,10 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) fix_scan_list(glob, splan->indexqual, rtoffset); splan->indexqualorig = fix_scan_list(glob, splan->indexqualorig, rtoffset); + splan->indexorderby = + fix_scan_list(glob, splan->indexorderby, rtoffset); + splan->indexorderbyorig = + fix_scan_list(glob, splan->indexorderbyorig, rtoffset); } break; case T_BitmapIndexScan: @@ -405,7 +411,6 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) case T_RemoteQuery: { RemoteQuery *splan = (RemoteQuery *) plan; - splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset); @@ -414,6 +419,17 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) } break; #endif + case T_ForeignScan: + { + ForeignScan *splan = (ForeignScan *) plan; + + splan->scan.scanrelid += rtoffset; + splan->scan.plan.targetlist = + fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset); + splan->scan.plan.qual = + fix_scan_list(glob, splan->scan.plan.qual, rtoffset); + } + break; case T_NestLoop: case T_MergeJoin: case T_HashJoin: @@ -552,6 +568,17 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) (Plan *) lfirst(l), rtoffset); } + + /* + * Append this ModifyTable node's final result relation RT + * index(es) to the global list for the plan, and set its + * resultRelIndex to reflect their starting position in the + * global list. + */ + splan->resultRelIndex = list_length(glob->resultRelations); + glob->resultRelations = + list_concat(glob->resultRelations, + list_copy(splan->resultRelations)); } break; case T_Append: @@ -572,6 +599,24 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) } } break; + case T_MergeAppend: + { + MergeAppend *splan = (MergeAppend *) plan; + + /* + * MergeAppend, like Sort et al, doesn't actually evaluate its + * targetlist or check quals. + */ + set_dummy_tlist_references(plan, rtoffset); + Assert(splan->plan.qual == NIL); + foreach(l, splan->mergeplans) + { + lfirst(l) = set_plan_refs(glob, + (Plan *) lfirst(l), + rtoffset); + } + } + break; case T_RecursiveUnion: /* This doesn't evaluate targetlist or check quals either */ set_dummy_tlist_references(plan, rtoffset); @@ -894,12 +939,11 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context) Assert(var->varlevelsup == 0); /* - * We should not see any Vars marked INNER, but in a nestloop inner - * scan there could be OUTER Vars. Leave them alone. + * We should not see any Vars marked INNER or OUTER. */ Assert(var->varno != INNER); - if (var->varno > 0 && var->varno != OUTER) - var->varno += context->rtoffset; + Assert(var->varno != OUTER); + var->varno += context->rtoffset; if (var->varnoold > 0) var->varnoold += context->rtoffset; return (Node *) var; @@ -943,10 +987,6 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context) * values to the result domain number of either the corresponding outer * or inner join tuple item. Also perform opcode lookup for these * expressions. and add regclass OIDs to glob->relationOids. - * - * In the case of a nestloop with inner indexscan, we will also need to - * apply the same transformation to any outer vars appearing in the - * quals of the child indexscan. set_inner_join_references does that. */ static void set_join_references(PlannerGlobal *glob, Join *join, int rtoffset) @@ -982,8 +1022,18 @@ set_join_references(PlannerGlobal *glob, Join *join, int rtoffset) /* Now do join-type-specific stuff */ if (IsA(join, NestLoop)) { - /* This processing is split out to handle possible recursion */ - set_inner_join_references(glob, inner_plan, outer_itlist); + NestLoop *nl = (NestLoop *) join; + ListCell *lc; + + foreach(lc, nl->nestParams) + { + NestLoopParam *nlp = (NestLoopParam *) lfirst(lc); + + nlp->paramval = (Var *) fix_upper_expr(glob, + (Node *) nlp->paramval, + outer_itlist, + rtoffset); + } } else if (IsA(join, MergeJoin)) { @@ -1013,193 +1063,6 @@ set_join_references(PlannerGlobal *glob, Join *join, int rtoffset) } /* - * set_inner_join_references - * Handle join references appearing in an inner indexscan's quals - * - * To handle bitmap-scan plan trees, we have to be able to recurse down - * to the bottom BitmapIndexScan nodes; likewise, appendrel indexscans - * require recursing through Append nodes. This is split out as a separate - * function so that it can recurse. - * - * Note we do *not* apply any rtoffset for non-join Vars; this is because - * the quals will be processed again by fix_scan_expr when the set_plan_refs - * recursion reaches the inner indexscan, and so we'd have done it twice. - */ -static void -set_inner_join_references(PlannerGlobal *glob, Plan *inner_plan, - indexed_tlist *outer_itlist) -{ - if (IsA(inner_plan, IndexScan)) - { - /* - * An index is being used to reduce the number of tuples scanned in - * the inner relation. If there are join clauses being used with the - * index, we must update their outer-rel var nodes to refer to the - * outer side of the join. - */ - IndexScan *innerscan = (IndexScan *) inner_plan; - List *indexqualorig = innerscan->indexqualorig; - - /* No work needed if indexqual refers only to its own rel... */ - if (NumRelids((Node *) indexqualorig) > 1) - { - Index innerrel = innerscan->scan.scanrelid; - - /* only refs to outer vars get changed in the inner qual */ - innerscan->indexqualorig = fix_join_expr(glob, - indexqualorig, - outer_itlist, - NULL, - innerrel, - 0); - innerscan->indexqual = fix_join_expr(glob, - innerscan->indexqual, - outer_itlist, - NULL, - innerrel, - 0); - - /* - * We must fix the inner qpqual too, if it has join clauses (this - * could happen if special operators are involved: some indexquals - * may get rechecked as qpquals). - */ - if (NumRelids((Node *) inner_plan->qual) > 1) - inner_plan->qual = fix_join_expr(glob, - inner_plan->qual, - outer_itlist, - NULL, - innerrel, - 0); - } - } - else if (IsA(inner_plan, BitmapIndexScan)) - { - /* - * Same, but index is being used within a bitmap plan. - */ - BitmapIndexScan *innerscan = (BitmapIndexScan *) inner_plan; - List *indexqualorig = innerscan->indexqualorig; - - /* No work needed if indexqual refers only to its own rel... */ - if (NumRelids((Node *) indexqualorig) > 1) - { - Index innerrel = innerscan->scan.scanrelid; - - /* only refs to outer vars get changed in the inner qual */ - innerscan->indexqualorig = fix_join_expr(glob, - indexqualorig, - outer_itlist, - NULL, - innerrel, - 0); - innerscan->indexqual = fix_join_expr(glob, - innerscan->indexqual, - outer_itlist, - NULL, - innerrel, - 0); - /* no need to fix inner qpqual */ - Assert(inner_plan->qual == NIL); - } - } - else if (IsA(inner_plan, BitmapHeapScan)) - { - /* - * The inner side is a bitmap scan plan. Fix the top node, and - * recurse to get the lower nodes. - * - * Note: create_bitmap_scan_plan removes clauses from bitmapqualorig - * if they are duplicated in qpqual, so must test these independently. - */ - BitmapHeapScan *innerscan = (BitmapHeapScan *) inner_plan; - Index innerrel = innerscan->scan.scanrelid; - List *bitmapqualorig = innerscan->bitmapqualorig; - - /* only refs to outer vars get changed in the inner qual */ - if (NumRelids((Node *) bitmapqualorig) > 1) - innerscan->bitmapqualorig = fix_join_expr(glob, - bitmapqualorig, - outer_itlist, - NULL, - innerrel, - 0); - - /* - * We must fix the inner qpqual too, if it has join clauses (this - * could happen if special operators are involved: some indexquals may - * get rechecked as qpquals). - */ - if (NumRelids((Node *) inner_plan->qual) > 1) - inner_plan->qual = fix_join_expr(glob, - inner_plan->qual, - outer_itlist, - NULL, - innerrel, - 0); - - /* Now recurse */ - set_inner_join_references(glob, inner_plan->lefttree, outer_itlist); - } - else if (IsA(inner_plan, BitmapAnd)) - { - /* All we need do here is recurse */ - BitmapAnd *innerscan = (BitmapAnd *) inner_plan; - ListCell *l; - - foreach(l, innerscan->bitmapplans) - { - set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist); - } - } - else if (IsA(inner_plan, BitmapOr)) - { - /* All we need do here is recurse */ - BitmapOr *innerscan = (BitmapOr *) inner_plan; - ListCell *l; - - foreach(l, innerscan->bitmapplans) - { - set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist); - } - } - else if (IsA(inner_plan, TidScan)) - { - TidScan *innerscan = (TidScan *) inner_plan; - Index innerrel = innerscan->scan.scanrelid; - - innerscan->tidquals = fix_join_expr(glob, - innerscan->tidquals, - outer_itlist, - NULL, - innerrel, - 0); - } - else if (IsA(inner_plan, Append)) - { - /* - * The inner side is an append plan. Recurse to see if it contains - * indexscans that need to be fixed. - */ - Append *appendplan = (Append *) inner_plan; - ListCell *l; - - foreach(l, appendplan->appendplans) - { - set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist); - } - } - else if (IsA(inner_plan, Result)) - { - /* Recurse through a gating Result node (similar to Append case) */ - Result *result = (Result *) inner_plan; - - if (result->plan.lefttree) - set_inner_join_references(glob, result->plan.lefttree, outer_itlist); - } -} - -/* * set_upper_references * Update the targetlist and quals of an upper-level plan node * to refer to the tuples returned by its lefttree subplan. @@ -1297,6 +1160,7 @@ set_dummy_tlist_references(Plan *plan, int rtoffset) tle->resno, exprType((Node *) oldvar), exprTypmod((Node *) oldvar), + exprCollation((Node *) oldvar), 0); if (IsA(oldvar, Var)) { @@ -1483,11 +1347,7 @@ search_indexed_tlist_for_non_var(Node *node, /* Found a matching subplan output expression */ Var *newvar; - newvar = makeVar(newvarno, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); + newvar = makeVarFromTargetEntry(newvarno, tle); newvar->varnoold = 0; /* wasn't ever a plain Var */ newvar->varoattno = 0; return newvar; @@ -1551,11 +1411,7 @@ search_indexed_tlist_for_sortgroupref(Node *node, /* Found a matching subplan output expression */ Var *newvar; - newvar = makeVar(newvarno, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); + newvar = makeVarFromTargetEntry(newvarno, tle); newvar->varnoold = 0; /* wasn't ever a plain Var */ newvar->varoattno = 0; return newvar; @@ -1574,23 +1430,21 @@ search_indexed_tlist_for_sortgroupref(Node *node, * * This is used in two different scenarios: a normal join clause, where * all the Vars in the clause *must* be replaced by OUTER or INNER references; - * and an indexscan being used on the inner side of a nestloop join. - * In the latter case we want to replace the outer-relation Vars by OUTER - * references, while Vars of the inner relation should be adjusted by rtoffset. - * (We also implement RETURNING clause fixup using this second scenario.) + * and a RETURNING clause, which may contain both Vars of the target relation + * and Vars of other relations. In the latter case we want to replace the + * other-relation Vars by OUTER references, while leaving target Vars alone. * * For a normal join, acceptable_rel should be zero so that any failure to - * match a Var will be reported as an error. For the indexscan case, - * pass inner_itlist = NULL and acceptable_rel = the (not-offseted-yet) ID - * of the inner relation. + * match a Var will be reported as an error. For the RETURNING case, pass + * inner_itlist = NULL and acceptable_rel = the ID of the target relation. * * 'clauses' is the targetlist or list of join clauses * 'outer_itlist' is the indexed target list of the outer join relation * 'inner_itlist' is the indexed target list of the inner join relation, * or NULL * 'acceptable_rel' is either zero or the rangetable index of a relation - * whose Vars may appear in the clause without provoking an error. - * 'rtoffset' is what to add to varno for Vars of acceptable_rel. + * whose Vars may appear in the clause without provoking an error + * 'rtoffset': how much to increment varnoold by * * Returns the new expression tree. The original clause structure is * not modified. @@ -1645,8 +1499,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) if (var->varno == context->acceptable_rel) { var = copyVar(var); - var->varno += context->rtoffset; - var->varnoold += context->rtoffset; + if (var->varnoold > 0) + var->varnoold += context->rtoffset; return (Node *) var; } @@ -1825,7 +1679,7 @@ set_returning_clause_references(PlannerGlobal *glob, /* * We can perform the desired Var fixup by abusing the fix_join_expr - * machinery that normally handles inner indexscan fixup. We search the + * machinery that formerly handled inner indexscan fixup. We search the * top plan's targetlist for Vars of non-result relations, and use * fix_join_expr to convert RETURNING Vars into references to those tlist * entries, while leaving result-rel Vars as-is. diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 8af94465e3..3af958cdad 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -3,11 +3,11 @@ * subselect.c * Planning routines for subselects and parameters. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.162 2010/04/19 00:55:25 rhaas Exp $ + * src/backend/optimizer/plan/subselect.c * *------------------------------------------------------------------------- */ @@ -85,30 +85,20 @@ static bool finalize_primnode(Node *node, finalize_primnode_context *context); /* - * Generate a Param node to replace the given Var, - * which is expected to have varlevelsup > 0 (ie, it is not local). + * Select a PARAM_EXEC number to identify the given Var. + * If the Var already has a param slot, return that one. */ -static Param * -replace_outer_var(PlannerInfo *root, Var *var) +static int +assign_param_for_var(PlannerInfo *root, Var *var) { - Param *retval; ListCell *ppl; PlannerParamItem *pitem; Index abslevel; int i; - Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); abslevel = root->query_level - var->varlevelsup; - /* - * If there's already a paramlist entry for this same Var, just use it. - * NOTE: in sufficiently complex querytrees, it is possible for the same - * varno/abslevel to refer to different RTEs in different parts of the - * parsetree, so that different fields might end up sharing the same Param - * number. As long as we check the vartype/typmod as well, I believe that - * this sort of aliasing will cause no trouble. The correct field should - * get stored into the Param slot at execution in each part of the tree. - */ + /* If there's already a paramlist entry for this same Var, just use it */ i = 0; foreach(ppl, root->glob->paramlist) { @@ -121,30 +111,84 @@ replace_outer_var(PlannerInfo *root, Var *var) pvar->varattno == var->varattno && pvar->vartype == var->vartype && pvar->vartypmod == var->vartypmod) - break; + return i; } i++; } - if (!ppl) - { - /* Nope, so make a new one */ - var = (Var *) copyObject(var); - var->varlevelsup = 0; + /* Nope, so make a new one */ + var = (Var *) copyObject(var); + var->varlevelsup = 0; - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) var; - pitem->abslevel = abslevel; + pitem = makeNode(PlannerParamItem); + pitem->item = (Node *) var; + pitem->abslevel = abslevel; - root->glob->paramlist = lappend(root->glob->paramlist, pitem); - /* i is already the correct index for the new item */ - } + root->glob->paramlist = lappend(root->glob->paramlist, pitem); + + /* i is already the correct list index for the new item */ + return i; +} + +/* + * Generate a Param node to replace the given Var, + * which is expected to have varlevelsup > 0 (ie, it is not local). + */ +static Param * +replace_outer_var(PlannerInfo *root, Var *var) +{ + Param *retval; + int i; + + Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); + + /* + * Find the Var in root->glob->paramlist, or add it if not present. + * + * NOTE: in sufficiently complex querytrees, it is possible for the same + * varno/abslevel to refer to different RTEs in different parts of the + * parsetree, so that different fields might end up sharing the same Param + * number. As long as we check the vartype/typmod as well, I believe that + * this sort of aliasing will cause no trouble. The correct field should + * get stored into the Param slot at execution in each part of the tree. + */ + i = assign_param_for_var(root, var); + + retval = makeNode(Param); + retval->paramkind = PARAM_EXEC; + retval->paramid = i; + retval->paramtype = var->vartype; + retval->paramtypmod = var->vartypmod; + retval->paramcollid = var->varcollid; + retval->location = -1; + + return retval; +} + +/* + * Generate a Param node to replace the given Var, which will be supplied + * from an upper NestLoop join node. + * + * Because we allow nestloop and subquery Params to alias each other, + * this is effectively the same as replace_outer_var, except that we expect + * the Var to be local to the current query level. + */ +Param * +assign_nestloop_param(PlannerInfo *root, Var *var) +{ + Param *retval; + int i; + + Assert(var->varlevelsup == 0); + + i = assign_param_for_var(root, var); retval = makeNode(Param); retval->paramkind = PARAM_EXEC; retval->paramid = i; retval->paramtype = var->vartype; retval->paramtypmod = var->vartypmod; + retval->paramcollid = var->varcollid; retval->location = -1; return retval; @@ -185,6 +229,7 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) retval->paramid = i; retval->paramtype = agg->aggtype; retval->paramtypmod = -1; + retval->paramcollid = agg->aggcollid; retval->location = -1; return retval; @@ -196,7 +241,8 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) * This is used to allocate PARAM_EXEC slots for subplan outputs. */ static Param * -generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod) +generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, + Oid paramcollation) { Param *retval; PlannerParamItem *pitem; @@ -206,6 +252,7 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod) retval->paramid = list_length(root->glob->paramlist); retval->paramtype = paramtype; retval->paramtypmod = paramtypmod; + retval->paramcollid = paramcollation; retval->location = -1; pitem = makeNode(PlannerParamItem); @@ -230,21 +277,23 @@ SS_assign_special_param(PlannerInfo *root) Param *param; /* We generate a Param of datatype INTERNAL */ - param = generate_new_param(root, INTERNALOID, -1); + param = generate_new_param(root, INTERNALOID, -1, InvalidOid); /* ... but the caller only cares about its ID */ return param->paramid; } /* - * Get the datatype of the first column of the plan's output. + * Get the datatype/typmod/collation of the first column of the plan's output. * - * This is stored for ARRAY_SUBLINK execution and for exprType()/exprTypmod(), - * which have no way to get at the plan associated with a SubPlan node. - * We really only need the info for EXPR_SUBLINK and ARRAY_SUBLINK subplans, - * but for consistency we save it always. + * This information is stored for ARRAY_SUBLINK execution and for + * exprType()/exprTypmod()/exprCollation(), which have no way to get at the + * plan associated with a SubPlan node. We really only need the info for + * EXPR_SUBLINK and ARRAY_SUBLINK subplans, but for consistency we save it + * always. */ static void -get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod) +get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod, + Oid *colcollation) { /* In cases such as EXISTS, tlist might be empty; arbitrarily use VOID */ if (plan->targetlist) @@ -256,11 +305,13 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod) { *coltype = exprType((Node *) tent->expr); *coltypmod = exprTypmod((Node *) tent->expr); + *colcollation = exprCollation((Node *) tent->expr); return; } } *coltype = VOIDOID; *coltypmod = -1; + *colcollation = InvalidOid; } /* @@ -435,7 +486,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, splan->subLinkType = subLinkType; splan->testexpr = NULL; splan->paramIds = NIL; - get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod); + get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod, + &splan->firstColCollation); splan->useHashTable = false; splan->unknownEqFalse = unknownEqFalse; splan->setParam = NIL; @@ -488,7 +540,7 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, Param *prm; Assert(testexpr == NULL); - prm = generate_new_param(root, BOOLOID, -1); + prm = generate_new_param(root, BOOLOID, -1, InvalidOid); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -502,7 +554,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, Assert(testexpr == NULL); prm = generate_new_param(root, exprType((Node *) te->expr), - exprTypmod((Node *) te->expr)); + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -521,7 +574,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, format_type_be(exprType((Node *) te->expr))); prm = generate_new_param(root, arraytype, - exprTypmod((Node *) te->expr)); + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -673,7 +727,8 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds) param = generate_new_param(root, exprType((Node *) tent->expr), - exprTypmod((Node *) tent->expr)); + exprTypmod((Node *) tent->expr), + exprCollation((Node *) tent->expr)); result = lappend(result, param); ids = lappend_int(ids, param->paramid); } @@ -702,11 +757,7 @@ generate_subquery_vars(PlannerInfo *root, List *tlist, Index varno) if (tent->resjunk) continue; - var = makeVar(varno, - tent->resno, - exprType((Node *) tent->expr), - exprTypmod((Node *) tent->expr), - 0); + var = makeVarFromTargetEntry(varno, tent); result = lappend(result, var); } @@ -830,28 +881,46 @@ testexpr_is_hashable(Node *testexpr) return false; } +/* + * Check expression is hashable + strict + * + * We could use op_hashjoinable() and op_strict(), but do it like this to + * avoid a redundant cache lookup. + */ static bool hash_ok_operator(OpExpr *expr) { Oid opid = expr->opno; - HeapTuple tup; - Form_pg_operator optup; /* quick out if not a binary operator */ if (list_length(expr->args) != 2) return false; - /* else must look up the operator properties */ - tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opid)); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for operator %u", opid); - optup = (Form_pg_operator) GETSTRUCT(tup); - if (!optup->oprcanhash || !func_strict(optup->oprcode)) + if (opid == ARRAY_EQ_OP) + { + /* array_eq is strict, but must check input type to ensure hashable */ + /* XXX record_eq will need same treatment when it becomes hashable */ + Node *leftarg = linitial(expr->args); + + return op_hashjoinable(opid, exprType(leftarg)); + } + else { + /* else must look up the operator properties */ + HeapTuple tup; + Form_pg_operator optup; + + tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for operator %u", opid); + optup = (Form_pg_operator) GETSTRUCT(tup); + if (!optup->oprcanhash || !func_strict(optup->oprcode)) + { + ReleaseSysCache(tup); + return false; + } ReleaseSysCache(tup); - return false; + return true; } - ReleaseSysCache(tup); - return true; } @@ -873,6 +942,7 @@ SS_process_ctes(PlannerInfo *root) foreach(lc, root->parse->cteList) { CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + CmdType cmdType = ((Query *) cte->ctequery)->commandType; Query *subquery; Plan *plan; PlannerInfo *subroot; @@ -882,9 +952,9 @@ SS_process_ctes(PlannerInfo *root) Param *prm; /* - * Ignore CTEs that are not actually referenced anywhere. + * Ignore SELECT CTEs that are not actually referenced anywhere. */ - if (cte->cterefcount == 0) + if (cte->cterefcount == 0 && cmdType == CMD_SELECT) { /* Make a dummy entry in cte_plan_ids */ root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1); @@ -916,7 +986,8 @@ SS_process_ctes(PlannerInfo *root) splan->subLinkType = CTE_SUBLINK; splan->testexpr = NULL; splan->paramIds = NIL; - get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod); + get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod, + &splan->firstColCollation); splan->useHashTable = false; splan->unknownEqFalse = false; splan->setParam = NIL; @@ -951,7 +1022,7 @@ SS_process_ctes(PlannerInfo *root) * Assign a param to represent the query output. We only really care * about reserving a parameter ID number. */ - prm = generate_new_param(root, INTERNALOID, -1); + prm = generate_new_param(root, INTERNALOID, -1, InvalidOid); splan->setParam = list_make1_int(prm->paramid); /* @@ -1002,6 +1073,11 @@ SS_process_ctes(PlannerInfo *root) * (Notionally, we replace the SubLink with a constant TRUE, then elide the * redundant constant from the qual.) * + * On success, the caller is also responsible for recursively applying + * pull_up_sublinks processing to the rarg and quals of the returned JoinExpr. + * (On failure, there is no need to do anything, since pull_up_sublinks will + * be applied when we recursively plan the sub-select.) + * * Side effects of a successful conversion include adding the SubLink's * subselect to the query's rangetable, so that it can be referenced in * the JoinExpr's rarg. @@ -1275,14 +1351,16 @@ simplify_EXISTS_query(Query *query) { /* * We don't try to simplify at all if the query uses set operations, - * aggregates, HAVING, LIMIT/OFFSET, or FOR UPDATE/SHARE; none of these - * seem likely in normal usage and their possible effects are complex. + * aggregates, modifying CTEs, HAVING, LIMIT/OFFSET, or FOR UPDATE/SHARE; + * none of these seem likely in normal usage and their possible effects + * are complex. */ if (query->commandType != CMD_SELECT || query->intoClause || query->setOperations || query->hasAggs || query->hasWindowFuncs || + query->hasModifyingCTE || query->havingQual || query->limitOffset || query->limitCount || @@ -1335,13 +1413,15 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, List *leftargs, *rightargs, *opids, + *opcollations, *newWhere, *tlist, *testlist, *paramids; ListCell *lc, *rc, - *oc; + *oc, + *cc; AttrNumber resno; /* @@ -1405,7 +1485,7 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, * we aren't trying hard yet to ensure that we have only outer or only * inner on each side; we'll check that if we get to the end. */ - leftargs = rightargs = opids = newWhere = NIL; + leftargs = rightargs = opids = opcollations = newWhere = NIL; foreach(lc, (List *) whereClause) { OpExpr *expr = (OpExpr *) lfirst(lc); @@ -1421,6 +1501,7 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, leftargs = lappend(leftargs, leftarg); rightargs = lappend(rightargs, rightarg); opids = lappend_oid(opids, expr->opno); + opcollations = lappend_oid(opcollations, expr->inputcollid); continue; } if (contain_vars_of_level(rightarg, 1)) @@ -1437,6 +1518,7 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, leftargs = lappend(leftargs, rightarg); rightargs = lappend(rightargs, leftarg); opids = lappend_oid(opids, expr->opno); + opcollations = lappend_oid(opcollations, expr->inputcollid); continue; } /* If no commutator, no chance to optimize the WHERE clause */ @@ -1505,19 +1587,21 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, */ tlist = testlist = paramids = NIL; resno = 1; - /* there's no "for3" so we have to chase one of the lists manually */ - oc = list_head(opids); - forboth(lc, leftargs, rc, rightargs) + /* there's no "forfour" so we have to chase one of the lists manually */ + cc = list_head(opcollations); + forthree(lc, leftargs, rc, rightargs, oc, opids) { Node *leftarg = (Node *) lfirst(lc); Node *rightarg = (Node *) lfirst(rc); Oid opid = lfirst_oid(oc); + Oid opcollation = lfirst_oid(cc); Param *param; - oc = lnext(oc); + cc = lnext(cc); param = generate_new_param(root, exprType(rightarg), - exprTypmod(rightarg)); + exprTypmod(rightarg), + exprCollation(rightarg)); tlist = lappend(tlist, makeTargetEntry((Expr *) rightarg, resno++, @@ -1525,7 +1609,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, false)); testlist = lappend(testlist, make_opclause(opid, BOOLOID, false, - (Expr *) leftarg, (Expr *) param)); + (Expr *) leftarg, (Expr *) param, + InvalidOid, opcollation)); paramids = lappend_int(paramids, param->paramid); } @@ -1780,8 +1865,9 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans) * * Note: this is a bit overly generous since some parameters of upper * query levels might belong to query subtrees that don't include this - * query. However, valid_params is only a debugging crosscheck, so it - * doesn't seem worth expending lots of cycles to try to be exact. + * query, or might be nestloop params that won't be passed down at all. + * However, valid_params is only a debugging crosscheck, so it doesn't + * seem worth expending lots of cycles to try to be exact. */ valid_params = bms_copy(initSetParam); paramid = 0; @@ -1856,6 +1942,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, { finalize_primnode_context context; int locally_added_param; + Bitmapset *nestloop_params; + Bitmapset *child_params; if (plan == NULL) return NULL; @@ -1863,6 +1951,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.root = root; context.paramids = NULL; /* initialize set to empty */ locally_added_param = -1; /* there isn't one */ + nestloop_params = NULL; /* there aren't any */ /* * When we call finalize_primnode, context.paramids sets are automatically @@ -1890,10 +1979,13 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, case T_IndexScan: finalize_primnode((Node *) ((IndexScan *) plan)->indexqual, &context); + finalize_primnode((Node *) ((IndexScan *) plan)->indexorderby, + &context); /* * we need not look at indexqualorig, since it will have the same - * param references as indexqual. + * param references as indexqual. Likewise, we can ignore + * indexorderbyorig. */ context.paramids = bms_add_members(context.paramids, scan_params); break; @@ -1989,6 +2081,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_ForeignScan: + context.paramids = bms_add_members(context.paramids, scan_params); + break; + case T_ModifyTable: { ModifyTable *mtplan = (ModifyTable *) plan; @@ -2036,6 +2132,22 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, } break; + case T_MergeAppend: + { + ListCell *l; + + foreach(l, ((MergeAppend *) plan)->mergeplans) + { + context.paramids = + bms_add_members(context.paramids, + finalize_plan(root, + (Plan *) lfirst(l), + valid_params, + scan_params)); + } + } + break; + case T_BitmapAnd: { ListCell *l; @@ -2069,8 +2181,20 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, break; case T_NestLoop: - finalize_primnode((Node *) ((Join *) plan)->joinqual, - &context); + { + ListCell *l; + + finalize_primnode((Node *) ((Join *) plan)->joinqual, + &context); + /* collect set of params that will be passed to right child */ + foreach(l, ((NestLoop *) plan)->nestParams) + { + NestLoopParam *nlp = (NestLoopParam *) lfirst(l); + + nestloop_params = bms_add_member(nestloop_params, + nlp->paramno); + } + } break; case T_MergeJoin: @@ -2133,17 +2257,32 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, } /* Process left and right child plans, if any */ - context.paramids = bms_add_members(context.paramids, - finalize_plan(root, - plan->lefttree, - valid_params, - scan_params)); + child_params = finalize_plan(root, + plan->lefttree, + valid_params, + scan_params); + context.paramids = bms_add_members(context.paramids, child_params); - context.paramids = bms_add_members(context.paramids, - finalize_plan(root, - plan->righttree, - valid_params, - scan_params)); + if (nestloop_params) + { + /* right child can reference nestloop_params as well as valid_params */ + child_params = finalize_plan(root, + plan->righttree, + bms_union(nestloop_params, valid_params), + scan_params); + /* ... and they don't count as parameters used at my level */ + child_params = bms_difference(child_params, nestloop_params); + bms_free(nestloop_params); + } + else + { + /* easy case */ + child_params = finalize_plan(root, + plan->righttree, + valid_params, + scan_params); + } + context.paramids = bms_add_members(context.paramids, child_params); /* * Any locally generated parameter doesn't count towards its generating @@ -2251,7 +2390,7 @@ finalize_primnode(Node *node, finalize_primnode_context *context) /* * SS_make_initplan_from_plan - given a plan tree, make it an InitPlan * - * The plan is expected to return a scalar value of the indicated type. + * The plan is expected to return a scalar value of the given type/collation. * We build an EXPR_SUBLINK SubPlan node and put it into the initplan * list for the current query level. A Param that represents the initplan's * output is returned. @@ -2260,7 +2399,8 @@ finalize_primnode(Node *node, finalize_primnode_context *context) */ Param * SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, - Oid resulttype, int32 resulttypmod) + Oid resulttype, int32 resulttypmod, + Oid resultcollation) { SubPlan *node; Param *prm; @@ -2296,7 +2436,8 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, */ node = makeNode(SubPlan); node->subLinkType = EXPR_SUBLINK; - get_first_col_type(plan, &node->firstColType, &node->firstColTypmod); + get_first_col_type(plan, &node->firstColType, &node->firstColTypmod, + &node->firstColCollation); node->plan_id = list_length(root->glob->subplans); root->init_plans = lappend(root->init_plans, node); @@ -2311,7 +2452,7 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, /* * Make a Param that will be the subplan's output. */ - prm = generate_new_param(root, resulttype, resulttypmod); + prm = generate_new_param(root, resulttype, resulttypmod, resultcollation); node->setParam = list_make1_int(prm->paramid); /* Label the subplan for EXPLAIN purposes */ diff --git a/src/backend/optimizer/prep/Makefile b/src/backend/optimizer/prep/Makefile index 13bd3394c6..86301bfbd3 100644 --- a/src/backend/optimizer/prep/Makefile +++ b/src/backend/optimizer/prep/Makefile @@ -4,7 +4,7 @@ # Makefile for optimizer/prep # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/optimizer/prep/Makefile,v 1.17 2008/02/19 10:30:07 petere Exp $ +# src/backend/optimizer/prep/Makefile # #------------------------------------------------------------------------- diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index dbe7836b42..5d163292c5 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -7,16 +7,17 @@ * pull_up_sublinks * inline_set_returning_functions * pull_up_subqueries + * flatten_simple_union_all * do expression preprocessing (including flattening JOIN alias vars) * reduce_outer_joins * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.73 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/prep/prepjointree.c * *------------------------------------------------------------------------- */ @@ -317,6 +318,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, { SubLink *sublink = (SubLink *) node; JoinExpr *j; + Relids child_rels; /* Is it a convertible ANY or EXISTS clause? */ if (sublink->subLinkType == ANY_SUBLINK) @@ -325,7 +327,18 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, available_rels); if (j) { - /* Yes, insert the new join node into the join tree */ + /* Yes; recursively process what we pulled up */ + j->rarg = pull_up_sublinks_jointree_recurse(root, + j->rarg, + &child_rels); + /* Pulled-up ANY/EXISTS quals can use those rels too */ + child_rels = bms_add_members(child_rels, available_rels); + /* ... and any inserted joins get stacked onto j->rarg */ + j->quals = pull_up_sublinks_qual_recurse(root, + j->quals, + child_rels, + &j->rarg); + /* Now insert the new join node into the join tree */ j->larg = *jtlink; *jtlink = (Node *) j; /* and return NULL representing constant TRUE */ @@ -338,7 +351,18 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, available_rels); if (j) { - /* Yes, insert the new join node into the join tree */ + /* Yes; recursively process what we pulled up */ + j->rarg = pull_up_sublinks_jointree_recurse(root, + j->rarg, + &child_rels); + /* Pulled-up ANY/EXISTS quals can use those rels too */ + child_rels = bms_add_members(child_rels, available_rels); + /* ... and any inserted joins get stacked onto j->rarg */ + j->quals = pull_up_sublinks_qual_recurse(root, + j->quals, + child_rels, + &j->rarg); + /* Now insert the new join node into the join tree */ j->larg = *jtlink; *jtlink = (Node *) j; /* and return NULL representing constant TRUE */ @@ -353,6 +377,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, /* If the immediate argument of NOT is EXISTS, try to convert */ SubLink *sublink = (SubLink *) get_notclausearg((Expr *) node); JoinExpr *j; + Relids child_rels; if (sublink && IsA(sublink, SubLink)) { @@ -362,7 +387,18 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, available_rels); if (j) { - /* Yes, insert the new join node into the join tree */ + /* Yes; recursively process what we pulled up */ + j->rarg = pull_up_sublinks_jointree_recurse(root, + j->rarg, + &child_rels); + /* Pulled-up ANY/EXISTS quals can use those rels too */ + child_rels = bms_add_members(child_rels, available_rels); + /* ... and any inserted joins get stacked onto j->rarg */ + j->quals = pull_up_sublinks_qual_recurse(root, + j->quals, + child_rels, + &j->rarg); + /* Now insert the new join node into the join tree */ j->larg = *jtlink; *jtlink = (Node *) j; /* and return NULL representing constant TRUE */ @@ -444,6 +480,7 @@ inline_set_returning_functions(PlannerInfo *root) rte->funcexpr = NULL; rte->funccoltypes = NIL; rte->funccoltypmods = NIL; + rte->funccolcollations = NIL; } } } @@ -869,11 +906,6 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) List *rtable; /* - * Append the subquery rtable entries to upper query. - */ - rtoffset = list_length(root->parse->rtable); - - /* * Append child RTEs to parent rtable. * * Upper-level vars in subquery are now one level closer to their parent @@ -881,6 +913,7 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) * because any such vars must refer to stuff above the level of the query * we are pulling into. */ + rtoffset = list_length(root->parse->rtable); rtable = copyObject(subquery->rtable); IncrementVarSublevelsUp_rtable(rtable, -1, 1); root->parse->rtable = list_concat(root->parse->rtable, rtable); @@ -888,7 +921,7 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) /* * Recursively scan the subquery's setOperations tree and add * AppendRelInfo nodes for leaf subqueries to the parent's - * append_rel_list. + * append_rel_list. Also apply pull_up_subqueries to the leaf subqueries. */ Assert(subquery->setOperations); pull_up_union_leaf_queries(subquery->setOperations, root, varno, subquery, @@ -905,14 +938,20 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) /* * pull_up_union_leaf_queries -- recursive guts of pull_up_simple_union_all * - * Note that setOpQuery is the Query containing the setOp node, whose rtable - * is where to look up the RTE if setOp is a RangeTblRef. This is *not* the - * same as root->parse, which is the top-level Query we are pulling up into. + * Build an AppendRelInfo for each leaf query in the setop tree, and then + * apply pull_up_subqueries to the leaf query. + * + * Note that setOpQuery is the Query containing the setOp node, whose tlist + * contains references to all the setop output columns. When called from + * pull_up_simple_union_all, this is *not* the same as root->parse, which is + * the parent Query we are pulling up into. * * parentRTindex is the appendrel parent's index in root->parse->rtable. * - * The child RTEs have already been copied to the parent. childRToffset - * tells us where in the parent's range table they were copied. + * The child RTEs have already been copied to the parent. childRToffset + * tells us where in the parent's range table they were copied. When called + * from flatten_simple_union_all, childRToffset is 0 since the child RTEs + * were already in root->parse->rtable and no RT index adjustment is needed. */ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, @@ -991,11 +1030,7 @@ make_setop_translation_list(Query *query, Index newvarno, if (tle->resjunk) continue; - vars = lappend(vars, makeVar(newvarno, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0)); + vars = lappend(vars, makeVarFromTargetEntry(newvarno, tle)); } *translated_vars = vars; @@ -1136,7 +1171,7 @@ is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes) Assert(subquery != NULL); /* Leaf nodes are OK if they match the toplevel column types */ - /* We don't have to compare typmods here */ + /* We don't have to compare typmods or collations here */ return tlist_same_datatypes(subquery->targetList, colTypes, true); } else if (IsA(setOp, SetOperationStmt)) @@ -1422,6 +1457,102 @@ pullup_replace_vars_callback(Var *var, return newnode; } + +/* + * flatten_simple_union_all + * Try to optimize top-level UNION ALL structure into an appendrel + * + * If a query's setOperations tree consists entirely of simple UNION ALL + * operations, flatten it into an append relation, which we can process more + * intelligently than the general setops case. Otherwise, do nothing. + * + * In most cases, this can succeed only for a top-level query, because for a + * subquery in FROM, the parent query's invocation of pull_up_subqueries would + * already have flattened the UNION via pull_up_simple_union_all. But there + * are a few cases we can support here but not in that code path, for example + * when the subquery also contains ORDER BY. + */ +void +flatten_simple_union_all(PlannerInfo *root) +{ + Query *parse = root->parse; + SetOperationStmt *topop; + Node *leftmostjtnode; + int leftmostRTI; + RangeTblEntry *leftmostRTE; + int childRTI; + RangeTblEntry *childRTE; + RangeTblRef *rtr; + + /* Shouldn't be called unless query has setops */ + topop = (SetOperationStmt *) parse->setOperations; + Assert(topop && IsA(topop, SetOperationStmt)); + + /* Can't optimize away a recursive UNION */ + if (root->hasRecursion) + return; + + /* + * Recursively check the tree of set operations. If not all UNION ALL + * with identical column types, punt. + */ + if (!is_simple_union_all_recurse((Node *) topop, parse, topop->colTypes)) + return; + + /* + * Locate the leftmost leaf query in the setops tree. The upper query's + * Vars all refer to this RTE (see transformSetOperationStmt). + */ + leftmostjtnode = topop->larg; + while (leftmostjtnode && IsA(leftmostjtnode, SetOperationStmt)) + leftmostjtnode = ((SetOperationStmt *) leftmostjtnode)->larg; + Assert(leftmostjtnode && IsA(leftmostjtnode, RangeTblRef)); + leftmostRTI = ((RangeTblRef *) leftmostjtnode)->rtindex; + leftmostRTE = rt_fetch(leftmostRTI, parse->rtable); + Assert(leftmostRTE->rtekind == RTE_SUBQUERY); + + /* + * Make a copy of the leftmost RTE and add it to the rtable. This copy + * will represent the leftmost leaf query in its capacity as a member of + * the appendrel. The original will represent the appendrel as a whole. + * (We must do things this way because the upper query's Vars have to be + * seen as referring to the whole appendrel.) + */ + childRTE = copyObject(leftmostRTE); + parse->rtable = lappend(parse->rtable, childRTE); + childRTI = list_length(parse->rtable); + + /* Modify the setops tree to reference the child copy */ + ((RangeTblRef *) leftmostjtnode)->rtindex = childRTI; + + /* Modify the formerly-leftmost RTE to mark it as an appendrel parent */ + leftmostRTE->inh = true; + + /* + * Form a RangeTblRef for the appendrel, and insert it into FROM. The top + * Query of a setops tree should have had an empty FromClause initially. + */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = leftmostRTI; + Assert(parse->jointree->fromlist == NIL); + parse->jointree->fromlist = list_make1(rtr); + + /* + * Now pretend the query has no setops. We must do this before trying to + * do subquery pullup, because of Assert in pull_up_simple_subquery. + */ + parse->setOperations = NULL; + + /* + * Build AppendRelInfo information, and apply pull_up_subqueries to the + * leaf queries of the UNION ALL. (We must do that now because they + * weren't previously referenced by the jointree, and so were missed by + * the main invocation of pull_up_subqueries.) + */ + pull_up_union_leaf_queries((Node *) topop, root, leftmostRTI, parse, 0); +} + + /* * reduce_outer_joins * Attempt to reduce outer joins to plain inner joins. @@ -1745,6 +1876,11 @@ reduce_outer_joins_pass2(Node *jtnode, * is that we pass either the local or the upper constraints, * never both, to the children of an outer join. * + * Note that a SEMI join works like an inner join here: it's okay + * to pass down both local and upper constraints. (There can't be + * any upper constraints affecting its inner side, but it's not + * worth having a separate code path to avoid passing them.) + * * At a FULL join we just punt and pass nothing down --- is it * possible to be smarter? */ @@ -1754,7 +1890,7 @@ reduce_outer_joins_pass2(Node *jtnode, if (!computed_local_nonnullable_vars) local_nonnullable_vars = find_nonnullable_vars(j->quals); local_forced_null_vars = find_forced_null_vars(j->quals); - if (jointype == JOIN_INNER) + if (jointype == JOIN_INNER || jointype == JOIN_SEMI) { /* OK to merge upper and local constraints */ local_nonnullable_rels = bms_add_members(local_nonnullable_rels, @@ -1774,14 +1910,14 @@ reduce_outer_joins_pass2(Node *jtnode, if (left_state->contains_outer) { - if (jointype == JOIN_INNER) + if (jointype == JOIN_INNER || jointype == JOIN_SEMI) { /* pass union of local and upper constraints */ pass_nonnullable_rels = local_nonnullable_rels; pass_nonnullable_vars = local_nonnullable_vars; pass_forced_null_vars = local_forced_null_vars; } - else if (jointype != JOIN_FULL) /* ie, LEFT/SEMI/ANTI */ + else if (jointype != JOIN_FULL) /* ie, LEFT or ANTI */ { /* can't pass local constraints to non-nullable side */ pass_nonnullable_rels = nonnullable_rels; @@ -1874,6 +2010,7 @@ substitute_multiple_relids_walker(Node *node, Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); return expression_tree_walker(node, substitute_multiple_relids_walker, (void *) context); diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c index 33c77d81bf..f6f00c4ee9 100644 --- a/src/backend/optimizer/prep/prepqual.c +++ b/src/backend/optimizer/prep/prepqual.c @@ -20,18 +20,19 @@ * tree after local transformations that might introduce nested AND/ORs. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepqual.c,v 1.61 2010/01/02 16:57:47 momjian Exp $ + * src/backend/optimizer/prep/prepqual.c * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" #include "utils/lsyscache.h" @@ -39,13 +40,231 @@ static List *pull_ands(List *andlist); static List *pull_ors(List *orlist); -static Expr *find_nots(Expr *qual); -static Expr *push_nots(Expr *qual); static Expr *find_duplicate_ors(Expr *qual); static Expr *process_duplicate_ors(List *orlist); /* + * negate_clause + * Negate a Boolean expression. + * + * Input is a clause to be negated (e.g., the argument of a NOT clause). + * Returns a new clause equivalent to the negation of the given clause. + * + * Although this can be invoked on its own, it's mainly intended as a helper + * for eval_const_expressions(), and that context drives several design + * decisions. In particular, if the input is already AND/OR flat, we must + * preserve that property. We also don't bother to recurse in situations + * where we can assume that lower-level executions of eval_const_expressions + * would already have simplified sub-clauses of the input. + * + * The difference between this and a simple make_notclause() is that this + * tries to get rid of the NOT node by logical simplification. It's clearly + * always a win if the NOT node can be eliminated altogether. However, our + * use of DeMorgan's laws could result in having more NOT nodes rather than + * fewer. We do that unconditionally anyway, because in WHERE clauses it's + * important to expose as much top-level AND/OR structure as possible. + * Also, eliminating an intermediate NOT may allow us to flatten two levels + * of AND or OR together that we couldn't have otherwise. Finally, one of + * the motivations for doing this is to ensure that logically equivalent + * expressions will be seen as physically equal(), so we should always apply + * the same transformations. + */ +Node * +negate_clause(Node *node) +{ + if (node == NULL) /* should not happen */ + elog(ERROR, "can't negate an empty subexpression"); + switch (nodeTag(node)) + { + case T_Const: + { + Const *c = (Const *) node; + + /* NOT NULL is still NULL */ + if (c->constisnull) + return makeBoolConst(false, true); + /* otherwise pretty easy */ + return makeBoolConst(!DatumGetBool(c->constvalue), false); + } + break; + case T_OpExpr: + { + /* + * Negate operator if possible: (NOT (< A B)) => (>= A B) + */ + OpExpr *opexpr = (OpExpr *) node; + Oid negator = get_negator(opexpr->opno); + + if (negator) + { + OpExpr *newopexpr = makeNode(OpExpr); + + newopexpr->opno = negator; + newopexpr->opfuncid = InvalidOid; + newopexpr->opresulttype = opexpr->opresulttype; + newopexpr->opretset = opexpr->opretset; + newopexpr->opcollid = opexpr->opcollid; + newopexpr->inputcollid = opexpr->inputcollid; + newopexpr->args = opexpr->args; + newopexpr->location = opexpr->location; + return (Node *) newopexpr; + } + } + break; + case T_ScalarArrayOpExpr: + { + /* + * Negate a ScalarArrayOpExpr if its operator has a negator; + * for example x = ANY (list) becomes x <> ALL (list) + */ + ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) node; + Oid negator = get_negator(saopexpr->opno); + + if (negator) + { + ScalarArrayOpExpr *newopexpr = makeNode(ScalarArrayOpExpr); + + newopexpr->opno = negator; + newopexpr->opfuncid = InvalidOid; + newopexpr->useOr = !saopexpr->useOr; + newopexpr->inputcollid = saopexpr->inputcollid; + newopexpr->args = saopexpr->args; + newopexpr->location = saopexpr->location; + return (Node *) newopexpr; + } + } + break; + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + + switch (expr->boolop) + { + /*-------------------- + * Apply DeMorgan's Laws: + * (NOT (AND A B)) => (OR (NOT A) (NOT B)) + * (NOT (OR A B)) => (AND (NOT A) (NOT B)) + * i.e., swap AND for OR and negate each subclause. + * + * If the input is already AND/OR flat and has no NOT + * directly above AND or OR, this transformation preserves + * those properties. For example, if no direct child of + * the given AND clause is an AND or a NOT-above-OR, then + * the recursive calls of negate_clause() can't return any + * OR clauses. So we needn't call pull_ors() before + * building a new OR clause. Similarly for the OR case. + *-------------------- + */ + case AND_EXPR: + { + List *nargs = NIL; + ListCell *lc; + + foreach(lc, expr->args) + { + nargs = lappend(nargs, + negate_clause(lfirst(lc))); + } + return (Node *) make_orclause(nargs); + } + break; + case OR_EXPR: + { + List *nargs = NIL; + ListCell *lc; + + foreach(lc, expr->args) + { + nargs = lappend(nargs, + negate_clause(lfirst(lc))); + } + return (Node *) make_andclause(nargs); + } + break; + case NOT_EXPR: + + /* + * NOT underneath NOT: they cancel. We assume the + * input is already simplified, so no need to recurse. + */ + return (Node *) linitial(expr->args); + default: + elog(ERROR, "unrecognized boolop: %d", + (int) expr->boolop); + break; + } + } + break; + case T_NullTest: + { + NullTest *expr = (NullTest *) node; + + /* + * In the rowtype case, the two flavors of NullTest are *not* + * logical inverses, so we can't simplify. But it does work + * for scalar datatypes. + */ + if (!expr->argisrow) + { + NullTest *newexpr = makeNode(NullTest); + + newexpr->arg = expr->arg; + newexpr->nulltesttype = (expr->nulltesttype == IS_NULL ? + IS_NOT_NULL : IS_NULL); + newexpr->argisrow = expr->argisrow; + return (Node *) newexpr; + } + } + break; + case T_BooleanTest: + { + BooleanTest *expr = (BooleanTest *) node; + BooleanTest *newexpr = makeNode(BooleanTest); + + newexpr->arg = expr->arg; + switch (expr->booltesttype) + { + case IS_TRUE: + newexpr->booltesttype = IS_NOT_TRUE; + break; + case IS_NOT_TRUE: + newexpr->booltesttype = IS_TRUE; + break; + case IS_FALSE: + newexpr->booltesttype = IS_NOT_FALSE; + break; + case IS_NOT_FALSE: + newexpr->booltesttype = IS_FALSE; + break; + case IS_UNKNOWN: + newexpr->booltesttype = IS_NOT_UNKNOWN; + break; + case IS_NOT_UNKNOWN: + newexpr->booltesttype = IS_UNKNOWN; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) expr->booltesttype); + break; + } + return (Node *) newexpr; + } + break; + default: + /* else fall through */ + break; + } + + /* + * Otherwise we don't know how to simplify this, so just tack on an + * explicit NOT node. + */ + return (Node *) make_notclause((Expr *) node); +} + + +/* * canonicalize_qual * Convert a qualification expression to the most useful form. * @@ -72,18 +291,11 @@ canonicalize_qual(Expr *qual) return NULL; /* - * Push down NOTs. We do this only in the top-level boolean expression, - * without examining arguments of operators/functions. The main reason for - * doing this is to expose as much top-level AND/OR structure as we can, - * so there's no point in descending further. - */ - newqual = find_nots(qual); - - /* - * Pull up redundant subclauses in OR-of-AND trees. Again, we do this - * only within the top-level AND/OR structure. + * Pull up redundant subclauses in OR-of-AND trees. We do this only + * within the top-level AND/OR structure; there's no point in looking + * deeper. */ - newqual = find_duplicate_ors(newqual); + newqual = find_duplicate_ors(qual); return newqual; } @@ -154,147 +366,6 @@ pull_ors(List *orlist) } -/* - * find_nots - * Traverse the qualification, looking for NOTs to take care of. - * For NOT clauses, apply push_nots() to try to push down the NOT. - * For AND and OR clause types, simply recurse. Otherwise stop - * recursing (we do not worry about structure below the top AND/OR tree). - * - * Returns the modified qualification. AND/OR flatness is preserved. - */ -static Expr * -find_nots(Expr *qual) -{ - if (and_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, find_nots(lfirst(temp))); - return make_andclause(pull_ands(t_list)); - } - else if (or_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, find_nots(lfirst(temp))); - return make_orclause(pull_ors(t_list)); - } - else if (not_clause((Node *) qual)) - return push_nots(get_notclausearg(qual)); - else - return qual; -} - -/* - * push_nots - * Push down a NOT as far as possible. - * - * Input is an expression to be negated (e.g., the argument of a NOT clause). - * Returns a new qual equivalent to the negation of the given qual. - */ -static Expr * -push_nots(Expr *qual) -{ - if (is_opclause(qual)) - { - /* - * Negate an operator clause if possible: (NOT (< A B)) => (>= A B) - * Otherwise, retain the clause as it is (the NOT can't be pushed down - * any farther). - */ - OpExpr *opexpr = (OpExpr *) qual; - Oid negator = get_negator(opexpr->opno); - - if (negator) - { - OpExpr *newopexpr = makeNode(OpExpr); - - newopexpr->opno = negator; - newopexpr->opfuncid = InvalidOid; - newopexpr->opresulttype = opexpr->opresulttype; - newopexpr->opretset = opexpr->opretset; - newopexpr->args = opexpr->args; - newopexpr->location = opexpr->location; - return (Expr *) newopexpr; - } - else - return make_notclause(qual); - } - else if (qual && IsA(qual, ScalarArrayOpExpr)) - { - /* - * Negate a ScalarArrayOpExpr if there is a negator for its operator; - * for example x = ANY (list) becomes x <> ALL (list). Otherwise, - * retain the clause as it is (the NOT can't be pushed down any - * farther). - */ - ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) qual; - Oid negator = get_negator(saopexpr->opno); - - if (negator) - { - ScalarArrayOpExpr *newopexpr = makeNode(ScalarArrayOpExpr); - - newopexpr->opno = negator; - newopexpr->opfuncid = InvalidOid; - newopexpr->useOr = !saopexpr->useOr; - newopexpr->args = saopexpr->args; - newopexpr->location = saopexpr->location; - return (Expr *) newopexpr; - } - else - return make_notclause(qual); - } - else if (and_clause((Node *) qual)) - { - /*-------------------- - * Apply DeMorgan's Laws: - * (NOT (AND A B)) => (OR (NOT A) (NOT B)) - * (NOT (OR A B)) => (AND (NOT A) (NOT B)) - * i.e., swap AND for OR and negate all the subclauses. - *-------------------- - */ - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_orclause(pull_ors(t_list)); - } - else if (or_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_andclause(pull_ands(t_list)); - } - else if (not_clause((Node *) qual)) - { - /* - * Another NOT cancels this NOT, so eliminate the NOT and stop - * negating this branch. But search the subexpression for more NOTs - * to simplify. - */ - return find_nots(get_notclausearg(qual)); - } - else - { - /* - * We don't know how to negate anything else, place a NOT at this - * level. No point in recursing deeper, either. - */ - return make_notclause(qual); - } -} - - /*-------------------- * The following code attempts to apply the inverse OR distributive law: * ((A AND B) OR (A AND C)) => (A AND (B OR C)) diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index abbf42cb62..c97150c6f7 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -3,21 +3,21 @@ * preptlist.c * Routines to preprocess the parse tree target list * - * This module takes care of altering the query targetlist as needed for - * INSERT, UPDATE, and DELETE queries. For INSERT and UPDATE queries, - * the targetlist must contain an entry for each attribute of the target - * relation in the correct order. For both UPDATE and DELETE queries, - * we need a junk targetlist entry holding the CTID attribute --- the - * executor relies on this to find the tuple to be replaced/deleted. - * We may also need junk tlist entries for Vars used in the RETURNING list - * and row ID information needed for EvalPlanQual checking. + * For INSERT and UPDATE queries, the targetlist must contain an entry for + * each attribute of the target relation in the correct order. For all query + * types, we may need to add junk tlist entries for Vars used in the RETURNING + * list and row ID information needed for EvalPlanQual checking. * + * NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD + * routines also do preprocessing of the targetlist. The division of labor + * between here and there is a bit arbitrary and historical. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.100 2010/02/26 02:00:46 momjian Exp $ + * src/backend/optimizer/prep/preptlist.c * *------------------------------------------------------------------------- */ @@ -78,41 +78,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) result_relation, range_table); /* - * for "update" and "delete" queries, add ctid of the result relation into - * the target list so that the ctid will propagate through execution and - * ExecutePlan() will be able to identify the right tuple to replace or - * delete. This extra field is marked "junk" so that it is not stored - * back into the tuple. - */ - if (command_type == CMD_UPDATE || command_type == CMD_DELETE) - { - TargetEntry *tle; - Var *var; - - var = makeVar(result_relation, SelfItemPointerAttributeNumber, - TIDOID, -1, 0); - - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup("ctid"), - true); - - /* - * For an UPDATE, expand_targetlist already created a fresh tlist. For - * DELETE, better do a listCopy so that we don't destructively modify - * the original tlist (is this really necessary?). - */ - if (command_type == CMD_DELETE) - tlist = list_copy(tlist); - - tlist = lappend(tlist, tle); - } - - /* * Add necessary junk columns for rowmarked rels. These values are needed * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual - * rechecking. While we are at it, store these junk attnos in the - * PlanRowMark list so that we don't have to redetermine them at runtime. + * rechecking. See comments for PlanRowMark in plannodes.h. */ foreach(lc, root->rowMarks) { @@ -121,18 +89,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) char resname[32]; TargetEntry *tle; - /* child rels should just use the same junk attrs as their parents */ + /* child rels use the same junk attrs as their parents */ if (rc->rti != rc->prti) - { - PlanRowMark *prc = get_plan_rowmark(root->rowMarks, rc->prti); - - /* parent should have appeared earlier in list */ - if (prc == NULL || prc->toidAttNo == InvalidAttrNumber) - elog(ERROR, "parent PlanRowMark not processed yet"); - rc->ctidAttNo = prc->ctidAttNo; - rc->toidAttNo = prc->toidAttNo; continue; - } if (rc->markType != ROW_MARK_COPY) { @@ -141,14 +100,14 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) SelfItemPointerAttributeNumber, TIDOID, -1, + InvalidOid, 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rti); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); tle = makeTargetEntry((Expr *) var, list_length(tlist) + 1, pstrdup(resname), true); tlist = lappend(tlist, tle); - rc->ctidAttNo = tle->resno; /* if parent of inheritance tree, need the tableoid too */ if (rc->isParent) @@ -157,31 +116,28 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) TableOidAttributeNumber, OIDOID, -1, + InvalidOid, 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rti); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); tle = makeTargetEntry((Expr *) var, list_length(tlist) + 1, pstrdup(resname), true); tlist = lappend(tlist, tle); - rc->toidAttNo = tle->resno; } } else { /* Not a table, so we need the whole row as a junk var */ - var = makeVar(rc->rti, - InvalidAttrNumber, - RECORDOID, - -1, - 0); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rti); + var = makeWholeRowVar(rt_fetch(rc->rti, range_table), + rc->rti, + 0); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); tle = makeTargetEntry((Expr *) var, list_length(tlist) + 1, pstrdup(resname), true); tlist = lappend(tlist, tle); - rc->wholeAttNo = tle->resno; } } @@ -235,9 +191,6 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) * Given a target list as generated by the parser and a result relation, * add targetlist entries for any missing attributes, and ensure the * non-junk attributes appear in proper field order. - * - * NOTE: if you are tempted to put more processing here, consider whether - * it shouldn't go in the rewriter's rewriteTargetList() instead. */ static List * expand_targetlist(List *tlist, int command_type, @@ -306,6 +259,7 @@ expand_targetlist(List *tlist, int command_type, */ Oid atttype = att_tup->atttypid; int32 atttypmod = att_tup->atttypmod; + Oid attcollation = att_tup->attcollation; Node *new_expr; switch (command_type) @@ -315,6 +269,7 @@ expand_targetlist(List *tlist, int command_type, { new_expr = (Node *) makeConst(atttype, -1, + attcollation, att_tup->attlen, (Datum) 0, true, /* isnull */ @@ -332,6 +287,7 @@ expand_targetlist(List *tlist, int command_type, /* Insert NULL for dropped column */ new_expr = (Node *) makeConst(INT4OID, -1, + InvalidOid, sizeof(int32), (Datum) 0, true, /* isnull */ @@ -345,6 +301,7 @@ expand_targetlist(List *tlist, int command_type, attrno, atttype, atttypmod, + attcollation, 0); } else @@ -352,6 +309,7 @@ expand_targetlist(List *tlist, int command_type, /* Insert NULL for dropped column */ new_expr = (Node *) makeConst(INT4OID, -1, + InvalidOid, sizeof(int32), (Datum) 0, true, /* isnull */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 3cb5ed977b..f82ab27b9a 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -17,12 +17,12 @@ * append relations, and thenceforth share code with the UNION ALL case. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.183 2010/07/06 19:18:56 momjian Exp $ + * src/backend/optimizer/prep/prepunion.c * *------------------------------------------------------------------------- */ @@ -54,7 +54,8 @@ static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root, double tuple_fraction, - List *colTypes, bool junkOK, + List *colTypes, List *colCollations, + bool junkOK, int flag, List *refnames_tlist, List **sortClauses, double *pNumGroups); static Plan *generate_recursion_plan(SetOperationStmt *setOp, @@ -81,12 +82,14 @@ static bool choose_hashed_setop(PlannerInfo *root, List *groupClauses, double dNumGroups, double dNumOutputRows, double tuple_fraction, const char *construct); -static List *generate_setop_tlist(List *colTypes, int flag, +static List *generate_setop_tlist(List *colTypes, List *colCollations, + int flag, Index varno, bool hack_constants, List *input_tlist, List *refnames_tlist); -static List *generate_append_tlist(List *colTypes, bool flag, +static List *generate_append_tlist(List *colTypes, List *colCollations, + bool flag, List *input_plans, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); @@ -169,7 +172,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction, * on upper-level nodes to deal with that). */ return recurse_set_operations((Node *) topop, root, tuple_fraction, - topop->colTypes, true, -1, + topop->colTypes, topop->colCollations, + true, -1, leftmostQuery->targetList, sortClauses, NULL); } @@ -179,7 +183,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction, * Recursively handle one step in a tree of set operations * * tuple_fraction: fraction of tuples we expect to retrieve from node - * colTypes: list of type OIDs of expected output columns + * colTypes: OID list of set-op's result column datatypes + * colCollations: OID list of set-op's result column collations * junkOK: if true, child resjunk columns may be left in the result * flag: if >= 0, add a resjunk output column indicating value of flag * refnames_tlist: targetlist to take column names from @@ -196,7 +201,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction, static Plan * recurse_set_operations(Node *setOp, PlannerInfo *root, double tuple_fraction, - List *colTypes, bool junkOK, + List *colTypes, List *colCollations, + bool junkOK, int flag, List *refnames_tlist, List **sortClauses, double *pNumGroups) { @@ -239,7 +245,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, * Add a SubqueryScan with the caller-requested targetlist */ plan = (Plan *) - make_subqueryscan(generate_setop_tlist(colTypes, flag, + make_subqueryscan(generate_setop_tlist(colTypes, colCollations, + flag, rtr->rtindex, true, subplan->targetlist, @@ -287,11 +294,13 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, * generate_setop_tlist() to use varno 0. */ if (flag >= 0 || - !tlist_same_datatypes(plan->targetlist, colTypes, junkOK)) + !tlist_same_datatypes(plan->targetlist, colTypes, junkOK) || + !tlist_same_collations(plan->targetlist, colCollations, junkOK)) { plan = (Plan *) make_result(root, - generate_setop_tlist(colTypes, flag, + generate_setop_tlist(colTypes, colCollations, + flag, 0, false, plan->targetlist, @@ -336,19 +345,21 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, * separately without any intention of combining them into one Append. */ lplan = recurse_set_operations(setOp->larg, root, tuple_fraction, - setOp->colTypes, false, -1, + setOp->colTypes, setOp->colCollations, + false, -1, refnames_tlist, sortClauses, NULL); /* The right plan will want to look at the left one ... */ root->non_recursive_plan = lplan; rplan = recurse_set_operations(setOp->rarg, root, tuple_fraction, - setOp->colTypes, false, -1, + setOp->colTypes, setOp->colCollations, + false, -1, refnames_tlist, sortClauses, NULL); root->non_recursive_plan = NULL; /* * Generate tlist for RecursiveUnion plan node --- same as in Append cases */ - tlist = generate_append_tlist(setOp->colTypes, false, + tlist = generate_append_tlist(setOp->colTypes, setOp->colCollations, false, list_make2(lplan, rplan), refnames_tlist); @@ -443,7 +454,7 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root, * concerned, but we must make it look real anyway for the benefit of the * next plan level up. */ - tlist = generate_append_tlist(op->colTypes, false, + tlist = generate_append_tlist(op->colTypes, op->colCollations, false, planlist, refnames_tlist); /* @@ -499,12 +510,14 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, /* Recurse on children, ensuring their outputs are marked */ lplan = recurse_set_operations(op->larg, root, 0.0 /* all tuples needed */ , - op->colTypes, false, 0, + op->colTypes, op->colCollations, + false, 0, refnames_tlist, &child_sortclauses, &dLeftGroups); rplan = recurse_set_operations(op->rarg, root, 0.0 /* all tuples needed */ , - op->colTypes, false, 1, + op->colTypes, op->colCollations, + false, 1, refnames_tlist, &child_sortclauses, &dRightGroups); @@ -534,7 +547,7 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, * column is shown as a variable not a constant, else setrefs.c will get * confused. */ - tlist = generate_append_tlist(op->colTypes, true, + tlist = generate_append_tlist(op->colTypes, op->colCollations, true, planlist, refnames_tlist); /* @@ -620,6 +633,13 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, * * NOTE: we can also pull a UNION ALL up into a UNION, since the distinct * output rows will be lost anyway. + * + * NOTE: currently, we ignore collations while determining if a child has + * the same properties. This is semantically sound only so long as all + * collations have the same notion of equality. It is valid from an + * implementation standpoint because we don't care about the ordering of + * a UNION child's result: UNION ALL results are always unordered, and + * generate_union_plan will force a fresh sort if the top level is a UNION. */ static List * recurse_union_children(Node *setOp, PlannerInfo *root, @@ -660,8 +680,10 @@ recurse_union_children(Node *setOp, PlannerInfo *root, */ return list_make1(recurse_set_operations(setOp, root, tuple_fraction, - top_union->colTypes, false, - -1, refnames_tlist, + top_union->colTypes, + top_union->colCollations, + false, -1, + refnames_tlist, &child_sortclauses, NULL)); } @@ -710,12 +732,12 @@ make_union_unique(SetOperationStmt *op, Plan *plan, plan->targetlist, NIL, AGG_HASHED, + NULL, list_length(groupList), extract_grouping_cols(groupList, plan->targetlist), extract_grouping_ops(groupList), numGroups, - 0, plan); /* Hashed aggregation produces randomly-ordered results */ *sortClauses = NIL; @@ -792,7 +814,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, * These path variables are dummies that just hold cost fields; we don't * make actual Paths for these steps. */ - cost_agg(&hashed_p, root, AGG_HASHED, 0, + cost_agg(&hashed_p, root, AGG_HASHED, NULL, numGroupCols, dNumGroups, input_plan->startup_cost, input_plan->total_cost, input_plan->plan_rows); @@ -805,7 +827,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, sorted_p.total_cost = input_plan->total_cost; /* XXX cost_sort doesn't actually look at pathkeys, so just pass NIL */ cost_sort(&sorted_p, root, NIL, sorted_p.total_cost, - input_plan->plan_rows, input_plan->plan_width, -1.0); + input_plan->plan_rows, input_plan->plan_width, + 0.0, work_mem, -1.0); cost_group(&sorted_p, root, numGroupCols, dNumGroups, sorted_p.startup_cost, sorted_p.total_cost, input_plan->plan_rows); @@ -829,7 +852,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, /* * Generate targetlist for a set-operation plan node * - * colTypes: column datatypes for non-junk columns + * colTypes: OID list of set-op's result column datatypes + * colCollations: OID list of set-op's result column collations * flag: -1 if no flag column needed, 0 or 1 to create a const flag column * varno: varno to use in generated Vars * hack_constants: true to copy up constants (see comments in code) @@ -837,7 +861,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, * refnames_tlist: targetlist to take column names from */ static List * -generate_setop_tlist(List *colTypes, int flag, +generate_setop_tlist(List *colTypes, List *colCollations, + int flag, Index varno, bool hack_constants, List *input_tlist, @@ -845,19 +870,23 @@ generate_setop_tlist(List *colTypes, int flag, { List *tlist = NIL; int resno = 1; - ListCell *i, - *j, - *k; + ListCell *ctlc, + *cclc, + *itlc, + *rtlc; TargetEntry *tle; Node *expr; - j = list_head(input_tlist); - k = list_head(refnames_tlist); - foreach(i, colTypes) + /* there's no forfour() so we must chase one list manually */ + rtlc = list_head(refnames_tlist); + forthree(ctlc, colTypes, cclc, colCollations, itlc, input_tlist) { - Oid colType = lfirst_oid(i); - TargetEntry *inputtle = (TargetEntry *) lfirst(j); - TargetEntry *reftle = (TargetEntry *) lfirst(k); + Oid colType = lfirst_oid(ctlc); + Oid colColl = lfirst_oid(cclc); + TargetEntry *inputtle = (TargetEntry *) lfirst(itlc); + TargetEntry *reftle = (TargetEntry *) lfirst(rtlc); + + rtlc = lnext(rtlc); Assert(inputtle->resno == resno); Assert(reftle->resno == resno); @@ -884,22 +913,50 @@ generate_setop_tlist(List *colTypes, int flag, inputtle->resno, exprType((Node *) inputtle->expr), exprTypmod((Node *) inputtle->expr), + exprCollation((Node *) inputtle->expr), 0); + if (exprType(expr) != colType) { + /* + * Note: it's not really cool to be applying coerce_to_common_type + * here; one notable point is that assign_expr_collations never + * gets run on any generated nodes. For the moment that's not a + * problem because we force the correct exposed collation below. + * It would likely be best to make the parser generate the correct + * output tlist for every set-op to begin with, though. + */ expr = coerce_to_common_type(NULL, /* no UNKNOWNs here */ expr, colType, "UNION/INTERSECT/EXCEPT"); } + + /* + * Ensure the tlist entry's exposed collation matches the set-op. This + * is necessary because plan_set_operations() reports the result + * ordering as a list of SortGroupClauses, which don't carry collation + * themselves but just refer to tlist entries. If we don't show the + * right collation then planner.c might do the wrong thing in + * higher-level queries. + * + * Note we use RelabelType, not CollateExpr, since this expression + * will reach the executor without any further processing. + */ + if (exprCollation(expr) != colColl) + { + expr = (Node *) makeRelabelType((Expr *) expr, + exprType(expr), + exprTypmod(expr), + colColl, + COERCE_DONTCARE); + } + tle = makeTargetEntry((Expr *) expr, (AttrNumber) resno++, pstrdup(reftle->resname), false); tlist = lappend(tlist, tle); - - j = lnext(j); - k = lnext(k); } if (flag >= 0) @@ -908,6 +965,7 @@ generate_setop_tlist(List *colTypes, int flag, /* flag value is the given constant */ expr = (Node *) makeConst(INT4OID, -1, + InvalidOid, sizeof(int4), Int32GetDatum(flag), false, @@ -925,23 +983,26 @@ generate_setop_tlist(List *colTypes, int flag, /* * Generate targetlist for a set-operation Append node * - * colTypes: column datatypes for non-junk columns + * colTypes: OID list of set-op's result column datatypes + * colCollations: OID list of set-op's result column collations * flag: true to create a flag column copied up from subplans * input_plans: list of sub-plans of the Append * refnames_tlist: targetlist to take column names from * * The entries in the Append's targetlist should always be simple Vars; - * we just have to make sure they have the right datatypes and typmods. + * we just have to make sure they have the right datatypes/typmods/collations. * The Vars are always generated with varno 0. */ static List * -generate_append_tlist(List *colTypes, bool flag, +generate_append_tlist(List *colTypes, List *colCollations, + bool flag, List *input_plans, List *refnames_tlist) { List *tlist = NIL; int resno = 1; ListCell *curColType; + ListCell *curColCollation; ListCell *ref_tl_item; int colindex; TargetEntry *tle; @@ -996,10 +1057,12 @@ generate_append_tlist(List *colTypes, bool flag, * Now we can build the tlist for the Append. */ colindex = 0; - forboth(curColType, colTypes, ref_tl_item, refnames_tlist) + forthree(curColType, colTypes, curColCollation, colCollations, + ref_tl_item, refnames_tlist) { Oid colType = lfirst_oid(curColType); int32 colTypmod = colTypmods[colindex++]; + Oid colColl = lfirst_oid(curColCollation); TargetEntry *reftle = (TargetEntry *) lfirst(ref_tl_item); Assert(reftle->resno == resno); @@ -1008,6 +1071,7 @@ generate_append_tlist(List *colTypes, bool flag, resno, colType, colTypmod, + colColl, 0); tle = makeTargetEntry((Expr *) expr, (AttrNumber) resno++, @@ -1024,6 +1088,7 @@ generate_append_tlist(List *colTypes, bool flag, resno, INT4OID, -1, + InvalidOid, 0); tle = makeTargetEntry((Expr *) expr, (AttrNumber) resno++, @@ -1288,13 +1353,10 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) newrc->rti = childRTindex; newrc->prti = rti; + newrc->rowmarkId = oldrc->rowmarkId; newrc->markType = oldrc->markType; newrc->noWait = oldrc->noWait; newrc->isParent = false; - /* junk attrs for children are not identified yet */ - newrc->ctidAttNo = InvalidAttrNumber; - newrc->toidAttNo = InvalidAttrNumber; - newrc->wholeAttNo = InvalidAttrNumber; root->rowMarks = lappend(root->rowMarks, newrc); } @@ -1327,7 +1389,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * Build the list of translations from parent Vars to child Vars for * an inheritance child. * - * For paranoia's sake, we match type as well as attribute name. + * For paranoia's sake, we match type/collation as well as attribute name. */ static void make_inh_translation_list(Relation oldrelation, Relation newrelation, @@ -1347,6 +1409,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, char *attname; Oid atttypid; int32 atttypmod; + Oid attcollation; int new_attno; att = old_tupdesc->attrs[old_attno]; @@ -1359,6 +1422,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, attname = NameStr(att->attname); atttypid = att->atttypid; atttypmod = att->atttypmod; + attcollation = att->attcollation; /* * When we are generating the "translation list" for the parent table @@ -1370,6 +1434,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, (AttrNumber) (old_attno + 1), atttypid, atttypmod, + attcollation, 0)); continue; } @@ -1403,15 +1468,19 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, attname, RelationGetRelationName(newrelation)); } - /* Found it, check type */ + /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", attname, RelationGetRelationName(newrelation)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, RelationGetRelationName(newrelation)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), atttypid, atttypmod, + attcollation, 0)); } @@ -1640,6 +1709,7 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); /* * We have to process RestrictInfo nodes specially. (Note: although @@ -1681,13 +1751,13 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) /* * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. */ newinfo->eval_cost.startup = -1; newinfo->norm_selec = -1; newinfo->outer_selec = -1; - newinfo->left_ec = NULL; - newinfo->right_ec = NULL; newinfo->left_em = NULL; newinfo->right_em = NULL; newinfo->scansel_cache = NIL; diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index d13b18c53f..3b2d16b635 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -4,7 +4,7 @@ # Makefile for optimizer/util # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/optimizer/util/Makefile,v 1.19 2008/10/21 20:42:53 tgl Exp $ +# src/backend/optimizer/util/Makefile # #------------------------------------------------------------------------- diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f6a943fcab..2914c39818 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3,12 +3,12 @@ * clauses.c * routines to manipulate qualification clauses * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.287 2010/03/19 22:54:41 tgl Exp $ + * src/backend/optimizer/util/clauses.c * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -50,6 +50,12 @@ typedef struct { + PlannerInfo *root; + AggClauseCosts *costs; +} count_agg_clauses_context; + +typedef struct +{ ParamListInfo boundParams; PlannerGlobal *glob; List *active_fns; @@ -79,7 +85,8 @@ typedef struct static bool contain_agg_clause_walker(Node *node, void *context); static bool pull_agg_clause_walker(Node *node, List **context); -static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts); +static bool count_agg_clauses_walker(Node *node, + count_agg_clauses_context *context); static bool find_window_functions_walker(Node *node, WindowFuncLists *lists); static bool expression_returns_set_rows_walker(Node *node, double *count); static bool contain_subplans_walker(Node *node, void *context); @@ -98,9 +105,10 @@ static List *simplify_or_arguments(List *args, static List *simplify_and_arguments(List *args, eval_const_expressions_context *context, bool *haveNull, bool *forceFalse); -static Expr *simplify_boolean_equality(Oid opno, List *args); +static Node *simplify_boolean_equality(Oid opno, List *args); static Expr *simplify_function(Oid funcid, - Oid result_type, int32 result_typmod, List **args, + Oid result_type, int32 result_typmod, + Oid result_collid, Oid input_collid, List **args, bool has_named_args, bool allow_inline, eval_const_expressions_context *context); @@ -113,11 +121,12 @@ static List *add_function_defaults(List *args, Oid result_type, static List *fetch_function_defaults(HeapTuple func_tuple); static void recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple); -static Expr *evaluate_function(Oid funcid, - Oid result_type, int32 result_typmod, List *args, +static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, + Oid result_collid, Oid input_collid, List *args, HeapTuple func_tuple, eval_const_expressions_context *context); -static Expr *inline_function(Oid funcid, Oid result_type, List *args, +static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid, + Oid input_collid, List *args, HeapTuple func_tuple, eval_const_expressions_context *context); static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, @@ -125,7 +134,8 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, static Node *substitute_actual_parameters_mutator(Node *node, substitute_actual_parameters_context *context); static void sql_inline_error_callback(void *arg); -static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod); +static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, + Oid result_collation); static Query *substitute_actual_srf_parameters(Query *expr, int nargs, List *args); static Node *substitute_actual_srf_parameters_mutator(Node *node, @@ -139,12 +149,14 @@ static bool tlist_matches_coltypelist(List *tlist, List *coltypelist); /* * make_opclause - * Creates an operator clause given its operator info, left operand, - * and right operand (pass NULL to create single-operand clause). + * Creates an operator clause given its operator info, left operand + * and right operand (pass NULL to create single-operand clause), + * and collation info. */ Expr * make_opclause(Oid opno, Oid opresulttype, bool opretset, - Expr *leftop, Expr *rightop) + Expr *leftop, Expr *rightop, + Oid opcollid, Oid inputcollid) { OpExpr *expr = makeNode(OpExpr); @@ -152,6 +164,8 @@ make_opclause(Oid opno, Oid opresulttype, bool opretset, expr->opfuncid = InvalidOid; expr->opresulttype = opresulttype; expr->opretset = opretset; + expr->opcollid = opcollid; + expr->inputcollid = inputcollid; if (rightop) expr->args = list_make2(leftop, rightop); else @@ -441,48 +455,80 @@ pull_agg_clause_walker(Node *node, List **context) /* * count_agg_clauses - * Recursively count the Aggref nodes in an expression tree. + * Recursively count the Aggref nodes in an expression tree, and + * accumulate other cost information about them too. * * Note: this also checks for nested aggregates, which are an error. * - * We not only count the nodes, but attempt to estimate the total space - * needed for their transition state values if all are evaluated in parallel - * (as would be done in a HashAgg plan). See AggClauseCounts for the exact - * set of statistics returned. + * We not only count the nodes, but estimate their execution costs, and + * attempt to estimate the total space needed for their transition state + * values if all are evaluated in parallel (as would be done in a HashAgg + * plan). See AggClauseCosts for the exact set of statistics collected. * - * NOTE that the counts are ADDED to those already in *counts ... so the - * caller is responsible for zeroing the struct initially. + * NOTE that the counts/costs are ADDED to those already in *costs ... so + * the caller is responsible for zeroing the struct initially. * * This does not descend into subqueries, and so should be used only after * reduction of sublinks to subplans, or in contexts where it's known there * are no subqueries. There mustn't be outer-aggregate references either. */ void -count_agg_clauses(Node *clause, AggClauseCounts *counts) +count_agg_clauses(PlannerInfo *root, Node *clause, AggClauseCosts *costs) { - /* no setup needed */ - count_agg_clauses_walker(clause, counts); + count_agg_clauses_context context; + + context.root = root; + context.costs = costs; + (void) count_agg_clauses_walker(clause, &context); } static bool -count_agg_clauses_walker(Node *node, AggClauseCounts *counts) +count_agg_clauses_walker(Node *node, count_agg_clauses_context *context) { if (node == NULL) return false; if (IsA(node, Aggref)) { Aggref *aggref = (Aggref *) node; - Oid *inputTypes; - int numArguments; + AggClauseCosts *costs = context->costs; HeapTuple aggTuple; Form_pg_aggregate aggform; + Oid aggtransfn; + Oid aggfinalfn; Oid aggtranstype; + QualCost argcosts; + Oid *inputTypes; + int numArguments; ListCell *l; Assert(aggref->agglevelsup == 0); - counts->numAggs++; + + /* fetch info about aggregate from pg_aggregate */ + aggTuple = SearchSysCache1(AGGFNOID, + ObjectIdGetDatum(aggref->aggfnoid)); + if (!HeapTupleIsValid(aggTuple)) + elog(ERROR, "cache lookup failed for aggregate %u", + aggref->aggfnoid); + aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); + aggtransfn = aggform->aggtransfn; + aggfinalfn = aggform->aggfinalfn; + aggtranstype = aggform->aggtranstype; + ReleaseSysCache(aggTuple); + + /* count it */ + costs->numAggs++; if (aggref->aggorder != NIL || aggref->aggdistinct != NIL) - counts->numOrderedAggs++; + costs->numOrderedAggs++; + + /* add component function execution costs to appropriate totals */ + costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost; + if (OidIsValid(aggfinalfn)) + costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost; + + /* also add the input expressions' cost to per-input-row costs */ + cost_qual_eval_node(&argcosts, (Node *) aggref->args, context->root); + costs->transCost.startup += argcosts.startup; + costs->transCost.per_tuple += argcosts.per_tuple; /* extract argument types (ignoring any ORDER BY expressions) */ inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args)); @@ -495,16 +541,6 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) inputTypes[numArguments++] = exprType((Node *) tle->expr); } - /* fetch aggregate transition datatype from pg_aggregate */ - aggTuple = SearchSysCache1(AGGFNOID, - ObjectIdGetDatum(aggref->aggfnoid)); - if (!HeapTupleIsValid(aggTuple)) - elog(ERROR, "cache lookup failed for aggregate %u", - aggref->aggfnoid); - aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); - aggtranstype = aggform->aggtranstype; - ReleaseSysCache(aggTuple); - /* resolve actual type of transition state, if polymorphic */ if (IsPolymorphicType(aggtranstype)) { @@ -547,7 +583,19 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod); avgwidth = MAXALIGN(avgwidth); - counts->transitionSpace += avgwidth + 2 * sizeof(void *); + costs->transitionSpace += avgwidth + 2 * sizeof(void *); + } + else if (aggtranstype == INTERNALOID) + { + /* + * INTERNAL transition type is a special case: although INTERNAL + * is pass-by-value, it's almost certainly being used as a pointer + * to some large data structure. We assume usage of + * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is + * being kept in a private memory context, as is done by + * array_agg() for instance. + */ + costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE; } /* @@ -566,7 +614,7 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) } Assert(!IsA(node, SubLink)); return expression_tree_walker(node, count_agg_clauses_walker, - (void *) counts); + (void *) context); } @@ -696,6 +744,8 @@ expression_returns_set_rows_walker(Node *node, double *count) return false; if (IsA(node, DistinctExpr)) return false; + if (IsA(node, NullIfExpr)) + return false; if (IsA(node, ScalarArrayOpExpr)) return false; if (IsA(node, BoolExpr)) @@ -718,8 +768,6 @@ expression_returns_set_rows_walker(Node *node, double *count) return false; if (IsA(node, XmlExpr)) return false; - if (IsA(node, NullIfExpr)) - return false; return expression_tree_walker(node, expression_returns_set_rows_walker, (void *) count); @@ -813,6 +861,15 @@ contain_mutable_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } + else if (IsA(node, NullIfExpr)) + { + NullIfExpr *expr = (NullIfExpr *) node; + + set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ + if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) + return true; + /* else fall through to check args */ + } else if (IsA(node, ScalarArrayOpExpr)) { ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; @@ -850,15 +907,6 @@ contain_mutable_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } - else if (IsA(node, NullIfExpr)) - { - NullIfExpr *expr = (NullIfExpr *) node; - - set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ - if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) - return true; - /* else fall through to check args */ - } else if (IsA(node, RowCompareExpr)) { RowCompareExpr *rcexpr = (RowCompareExpr *) node; @@ -928,6 +976,15 @@ contain_volatile_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } + else if (IsA(node, NullIfExpr)) + { + NullIfExpr *expr = (NullIfExpr *) node; + + set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ + if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) + return true; + /* else fall through to check args */ + } else if (IsA(node, ScalarArrayOpExpr)) { ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; @@ -965,15 +1022,6 @@ contain_volatile_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } - else if (IsA(node, NullIfExpr)) - { - NullIfExpr *expr = (NullIfExpr *) node; - - set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ - if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) - return true; - /* else fall through to check args */ - } else if (IsA(node, RowCompareExpr)) { /* RowCompare probably can't have volatile ops, but check anyway */ @@ -1058,6 +1106,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) /* IS DISTINCT FROM is inherently non-strict */ return true; } + if (IsA(node, NullIfExpr)) + return true; if (IsA(node, ScalarArrayOpExpr)) { ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; @@ -1106,8 +1156,6 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; if (IsA(node, XmlExpr)) return true; - if (IsA(node, NullIfExpr)) - return true; if (IsA(node, NullTest)) return true; if (IsA(node, BooleanTest)) @@ -1295,6 +1343,12 @@ find_nonnullable_rels_walker(Node *node, bool top_level) result = find_nonnullable_rels_walker((Node *) expr->arg, top_level); } + else if (IsA(node, CollateExpr)) + { + CollateExpr *expr = (CollateExpr *) node; + + result = find_nonnullable_rels_walker((Node *) expr->arg, top_level); + } else if (IsA(node, NullTest)) { /* IS NOT NULL can be considered strict, but only at top level */ @@ -1497,6 +1551,12 @@ find_nonnullable_vars_walker(Node *node, bool top_level) result = find_nonnullable_vars_walker((Node *) expr->arg, top_level); } + else if (IsA(node, CollateExpr)) + { + CollateExpr *expr = (CollateExpr *) node; + + result = find_nonnullable_vars_walker((Node *) expr->arg, top_level); + } else if (IsA(node, NullTest)) { /* IS NOT NULL can be considered strict, but only at top level */ @@ -1785,7 +1845,7 @@ CommuteOpExpr(OpExpr *clause) */ clause->opno = opoid; clause->opfuncid = InvalidOid; - /* opresulttype and opretset are assumed not to change */ + /* opresulttype, opretset, opcollid, inputcollid need not change */ temp = linitial(clause->args); linitial(clause->args) = lsecond(clause->args); @@ -1849,6 +1909,7 @@ CommuteRowCompareExpr(RowCompareExpr *clause) /* * Note: we need not change the opfamilies list; we assume any btree * opfamily containing an operator will also contain its commutator. + * Collations don't change either. */ temp = clause->largs; @@ -1961,7 +2022,8 @@ set_coercionform_dontcare_walker(Node *node, void *context) */ static bool rowtype_field_matches(Oid rowtypeid, int fieldnum, - Oid expectedtype, int32 expectedtypmod) + Oid expectedtype, int32 expectedtypmod, + Oid expectedcollation) { TupleDesc tupdesc; Form_pg_attribute attr; @@ -1978,7 +2040,8 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, attr = tupdesc->attrs[fieldnum - 1]; if (attr->attisdropped || attr->atttypid != expectedtype || - attr->atttypmod != expectedtypmod) + attr->atttypmod != expectedtypmod || + attr->attcollation != expectedcollation) { ReleaseTupleDesc(tupdesc); return false; @@ -2006,11 +2069,16 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, * will not be pre-evaluated here, although we will reduce their * arguments as far as possible. * + * Whenever a function is eliminated from the expression by means of + * constant-expression evaluation or inlining, we add the function to + * root->glob->invalItems. This ensures the plan is known to depend on + * such functions, even though they aren't referenced anymore. + * * We assume that the tree has already been type-checked and contains * only operators and functions that are reasonable to try to execute. * * NOTE: "root" can be passed as NULL if the caller never wants to do any - * Param substitutions. + * Param substitutions nor receive info about inlined functions. * * NOTE: the planner assumes that this will always flatten nested AND and * OR clauses into N-argument form. See comments in prepqual.c. @@ -2113,6 +2181,7 @@ eval_const_expressions_mutator(Node *node, pval = datumCopy(prm->value, typByVal, typLen); return (Node *) makeConst(param->paramtype, param->paramtypmod, + param->paramcollid, (int) typLen, pval, prm->isnull, @@ -2156,6 +2225,8 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->funcid, expr->funcresulttype, exprTypmod(node), + expr->funccollid, + expr->inputcollid, &args, has_named_args, true, context); if (simple) /* successfully simplified it */ @@ -2172,6 +2243,8 @@ eval_const_expressions_mutator(Node *node, newexpr->funcresulttype = expr->funcresulttype; newexpr->funcretset = expr->funcretset; newexpr->funcformat = expr->funcformat; + newexpr->funccollid = expr->funccollid; + newexpr->inputcollid = expr->inputcollid; newexpr->args = args; newexpr->location = expr->location; return (Node *) newexpr; @@ -2204,6 +2277,8 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, + expr->opcollid, + expr->inputcollid, &args, false, true, context); if (simple) /* successfully simplified it */ @@ -2217,7 +2292,7 @@ eval_const_expressions_mutator(Node *node, if (expr->opno == BooleanEqualOperator || expr->opno == BooleanNotEqualOperator) { - simple = simplify_boolean_equality(expr->opno, args); + simple = (Expr *) simplify_boolean_equality(expr->opno, args); if (simple) /* successfully simplified it */ return (Node *) simple; } @@ -2232,6 +2307,8 @@ eval_const_expressions_mutator(Node *node, newexpr->opfuncid = expr->opfuncid; newexpr->opresulttype = expr->opresulttype; newexpr->opretset = expr->opretset; + newexpr->opcollid = expr->opcollid; + newexpr->inputcollid = expr->inputcollid; newexpr->args = args; newexpr->location = expr->location; return (Node *) newexpr; @@ -2297,6 +2374,8 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, + expr->opcollid, + expr->inputcollid, &args, false, false, context); if (simple) /* successfully simplified it */ @@ -2324,6 +2403,8 @@ eval_const_expressions_mutator(Node *node, newexpr->opfuncid = expr->opfuncid; newexpr->opresulttype = expr->opresulttype; newexpr->opretset = expr->opretset; + newexpr->opcollid = expr->opcollid; + newexpr->inputcollid = expr->inputcollid; newexpr->args = args; newexpr->location = expr->location; return (Node *) newexpr; @@ -2383,24 +2464,12 @@ eval_const_expressions_mutator(Node *node, Assert(list_length(expr->args) == 1); arg = eval_const_expressions_mutator(linitial(expr->args), context); - if (IsA(arg, Const)) - { - Const *const_input = (Const *) arg; - - /* NOT NULL => NULL */ - if (const_input->constisnull) - return makeBoolConst(false, true); - /* otherwise pretty easy */ - return makeBoolConst(!DatumGetBool(const_input->constvalue), - false); - } - else if (not_clause(arg)) - { - /* Cancel NOT/NOT */ - return (Node *) get_notclausearg((Expr *) arg); - } - /* Else we still need a NOT node */ - return (Node *) make_notclause((Expr *) arg); + + /* + * Use negate_clause() to see if we can simplify away the + * NOT. + */ + return negate_clause(arg); } default: elog(ERROR, "unrecognized boolop: %d", @@ -2445,6 +2514,7 @@ eval_const_expressions_mutator(Node *node, con->consttype = relabel->resulttype; con->consttypmod = relabel->resulttypmod; + con->constcollid = relabel->resultcollid; return (Node *) con; } else @@ -2454,6 +2524,7 @@ eval_const_expressions_mutator(Node *node, newrelabel->arg = (Expr *) arg; newrelabel->resulttype = relabel->resulttype; newrelabel->resulttypmod = relabel->resulttypmod; + newrelabel->resultcollid = relabel->resultcollid; newrelabel->relabelformat = relabel->relabelformat; newrelabel->location = relabel->location; return (Node *) newrelabel; @@ -2483,12 +2554,17 @@ eval_const_expressions_mutator(Node *node, * then the result type's input function. So, try to simplify it as * though it were a stack of two such function calls. First we need * to know what the functions are. + * + * Note that the coercion functions are assumed not to care about + * input collation, so we just pass InvalidOid for that. */ getTypeOutputInfo(exprType((Node *) arg), &outfunc, &outtypisvarlena); getTypeInputInfo(expr->resulttype, &infunc, &intypioparam); simple = simplify_function(outfunc, CSTRINGOID, -1, + InvalidOid, + InvalidOid, &args, false, true, context); if (simple) /* successfully simplified output fn */ @@ -2498,15 +2574,17 @@ eval_const_expressions_mutator(Node *node, * all three, trusting that nothing downstream will complain. */ args = list_make3(simple, - makeConst(OIDOID, -1, sizeof(Oid), + makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(intypioparam), false, true), - makeConst(INT4OID, -1, sizeof(int32), - Int32GetDatum(-1), - false, true)); + makeConst(INT4OID, -1, InvalidOid, sizeof(int32), + Int32GetDatum(-1), + false, true)); simple = simplify_function(infunc, expr->resulttype, -1, + expr->resultcollid, + InvalidOid, &args, false, true, context); if (simple) /* successfully simplified input fn */ @@ -2521,6 +2599,7 @@ eval_const_expressions_mutator(Node *node, newexpr = makeNode(CoerceViaIO); newexpr->arg = arg; newexpr->resulttype = expr->resulttype; + newexpr->resultcollid = expr->resultcollid; newexpr->coerceformat = expr->coerceformat; newexpr->location = expr->location; return (Node *) newexpr; @@ -2543,6 +2622,7 @@ eval_const_expressions_mutator(Node *node, newexpr->elemfuncid = expr->elemfuncid; newexpr->resulttype = expr->resulttype; newexpr->resulttypmod = expr->resulttypmod; + newexpr->resultcollid = expr->resultcollid; newexpr->isExplicit = expr->isExplicit; newexpr->coerceformat = expr->coerceformat; newexpr->location = expr->location; @@ -2556,11 +2636,57 @@ eval_const_expressions_mutator(Node *node, func_volatile(newexpr->elemfuncid) == PROVOLATILE_IMMUTABLE)) return (Node *) evaluate_expr((Expr *) newexpr, newexpr->resulttype, - newexpr->resulttypmod); + newexpr->resulttypmod, + newexpr->resultcollid); /* Else we must return the partially-simplified node */ return (Node *) newexpr; } + if (IsA(node, CollateExpr)) + { + /* + * If we can simplify the input to a constant, then we don't need the + * CollateExpr node at all: just change the constcollid field of the + * Const node. Otherwise, replace the CollateExpr with a RelabelType. + * (We do that so as to improve uniformity of expression + * representation and thus simplify comparison of expressions.) + */ + CollateExpr *collate = (CollateExpr *) node; + Node *arg; + + arg = eval_const_expressions_mutator((Node *) collate->arg, + context); + + if (arg && IsA(arg, Const)) + { + Const *con = (Const *) arg; + + con->constcollid = collate->collOid; + return (Node *) con; + } + else if (collate->collOid == exprCollation(arg)) + { + /* Don't need a RelabelType either... */ + return arg; + } + else + { + RelabelType *relabel = makeNode(RelabelType); + + relabel->resulttype = exprType(arg); + relabel->resulttypmod = exprTypmod(arg); + relabel->resultcollid = collate->collOid; + relabel->relabelformat = COERCE_DONTCARE; + relabel->location = collate->location; + + /* Don't create stacked RelabelTypes */ + while (arg && IsA(arg, RelabelType)) + arg = (Node *) ((RelabelType *) arg)->arg; + relabel->arg = (Expr *) arg; + + return (Node *) relabel; + } + } if (IsA(node, CaseExpr)) { /*---------- @@ -2578,7 +2704,18 @@ eval_const_expressions_mutator(Node *node, * placeholder nodes, so that we have the opportunity to reduce * constant test conditions. For example this allows * CASE 0 WHEN 0 THEN 1 ELSE 1/0 END - * to reduce to 1 rather than drawing a divide-by-0 error. + * to reduce to 1 rather than drawing a divide-by-0 error. Note + * that when the test expression is constant, we don't have to + * include it in the resulting CASE; for example + * CASE 0 WHEN x THEN y ELSE z END + * is transformed by the parser to + * CASE 0 WHEN CaseTestExpr = x THEN y ELSE z END + * which we can simplify to + * CASE WHEN 0 = x THEN y ELSE z END + * It is not necessary for the executor to evaluate the "arg" + * expression when executing the CASE, since any contained + * CaseTestExprs that might have referred to it will have been + * replaced by the constant. *---------- */ CaseExpr *caseexpr = (CaseExpr *) node; @@ -2597,7 +2734,10 @@ eval_const_expressions_mutator(Node *node, /* Set up for contained CaseTestExpr nodes */ save_case_val = context->case_val; if (newarg && IsA(newarg, Const)) + { context->case_val = newarg; + newarg = NULL; /* not needed anymore, see comment above */ + } else context->case_val = NULL; @@ -2671,6 +2811,7 @@ eval_const_expressions_mutator(Node *node, /* Otherwise we need a new CASE node */ newcase = makeNode(CaseExpr); newcase->casetype = caseexpr->casetype; + newcase->casecollid = caseexpr->casecollid; newcase->arg = (Expr *) newarg; newcase->args = newargs; newcase->defresult = (Expr *) defresult; @@ -2711,6 +2852,7 @@ eval_const_expressions_mutator(Node *node, newarray = makeNode(ArrayExpr); newarray->array_typeid = arrayexpr->array_typeid; + newarray->array_collid = arrayexpr->array_collid; newarray->element_typeid = arrayexpr->element_typeid; newarray->elements = newelems; newarray->multidims = arrayexpr->multidims; @@ -2719,7 +2861,8 @@ eval_const_expressions_mutator(Node *node, if (all_const) return (Node *) evaluate_expr((Expr *) newarray, newarray->array_typeid, - exprTypmod(node)); + exprTypmod(node), + newarray->array_collid); return (Node *) newarray; } @@ -2741,7 +2884,9 @@ eval_const_expressions_mutator(Node *node, /* * We can remove null constants from the list. For a non-null * constant, if it has not been preceded by any other - * non-null-constant expressions then that is the result. + * non-null-constant expressions then it is the result. Otherwise, + * it's the next argument, but we can drop following arguments + * since they will never be reached. */ if (IsA(e, Const)) { @@ -2749,16 +2894,21 @@ eval_const_expressions_mutator(Node *node, continue; /* drop null constant */ if (newargs == NIL) return e; /* first expr */ + newargs = lappend(newargs, e); + break; } newargs = lappend(newargs, e); } /* If all the arguments were constant null, the result is just null */ if (newargs == NIL) - return (Node *) makeNullConst(coalesceexpr->coalescetype, -1); + return (Node *) makeNullConst(coalesceexpr->coalescetype, + -1, + coalesceexpr->coalescecollid); newcoalesce = makeNode(CoalesceExpr); newcoalesce->coalescetype = coalesceexpr->coalescetype; + newcoalesce->coalescecollid = coalesceexpr->coalescecollid; newcoalesce->args = newargs; newcoalesce->location = coalesceexpr->location; return (Node *) newcoalesce; @@ -2788,11 +2938,13 @@ eval_const_expressions_mutator(Node *node, if (rowtype_field_matches(((Var *) arg)->vartype, fselect->fieldnum, fselect->resulttype, - fselect->resulttypmod)) + fselect->resulttypmod, + fselect->resultcollid)) return (Node *) makeVar(((Var *) arg)->varno, fselect->fieldnum, fselect->resulttype, fselect->resulttypmod, + fselect->resultcollid, ((Var *) arg)->varlevelsup); } if (arg && IsA(arg, RowExpr)) @@ -2808,9 +2960,11 @@ eval_const_expressions_mutator(Node *node, if (rowtype_field_matches(rowexpr->row_typeid, fselect->fieldnum, fselect->resulttype, - fselect->resulttypmod) && + fselect->resulttypmod, + fselect->resultcollid) && fselect->resulttype == exprType(fld) && - fselect->resulttypmod == exprTypmod(fld)) + fselect->resulttypmod == exprTypmod(fld) && + fselect->resultcollid == exprCollation(fld)) return fld; } } @@ -2819,6 +2973,7 @@ eval_const_expressions_mutator(Node *node, newfselect->fieldnum = fselect->fieldnum; newfselect->resulttype = fselect->resulttype; newfselect->resulttypmod = fselect->resulttypmod; + newfselect->resultcollid = fselect->resultcollid; return (Node *) newfselect; } if (IsA(node, NullTest)) @@ -3210,11 +3365,11 @@ simplify_and_arguments(List *args, * We come here only if simplify_function has failed; therefore we cannot * see two constant inputs, nor a constant-NULL input. */ -static Expr * +static Node * simplify_boolean_equality(Oid opno, List *args) { - Expr *leftop; - Expr *rightop; + Node *leftop; + Node *rightop; Assert(list_length(args) == 2); leftop = linitial(args); @@ -3227,12 +3382,12 @@ simplify_boolean_equality(Oid opno, List *args) if (DatumGetBool(((Const *) leftop)->constvalue)) return rightop; /* true = foo */ else - return make_notclause(rightop); /* false = foo */ + return negate_clause(rightop); /* false = foo */ } else { if (DatumGetBool(((Const *) leftop)->constvalue)) - return make_notclause(rightop); /* true <> foo */ + return negate_clause(rightop); /* true <> foo */ else return rightop; /* false <> foo */ } @@ -3245,12 +3400,12 @@ simplify_boolean_equality(Oid opno, List *args) if (DatumGetBool(((Const *) rightop)->constvalue)) return leftop; /* foo = true */ else - return make_notclause(leftop); /* foo = false */ + return negate_clause(leftop); /* foo = false */ } else { if (DatumGetBool(((Const *) rightop)->constvalue)) - return make_notclause(leftop); /* foo <> true */ + return negate_clause(leftop); /* foo <> true */ else return leftop; /* foo <> false */ } @@ -3263,7 +3418,9 @@ simplify_boolean_equality(Oid opno, List *args) * (which might originally have been an operator; we don't care) * * Inputs are the function OID, actual result type OID (which is needed for - * polymorphic functions) and typmod, and the pre-simplified argument list; + * polymorphic functions), result typmod, result collation, + * the input collation to use for the function, + * the pre-simplified argument list, and some flags; * also the context data for eval_const_expressions. * * Returns a simplified expression if successful, or NULL if cannot @@ -3277,7 +3434,7 @@ simplify_boolean_equality(Oid opno, List *args) */ static Expr * simplify_function(Oid funcid, Oid result_type, int32 result_typmod, - List **args, + Oid result_collid, Oid input_collid, List **args, bool has_named_args, bool allow_inline, eval_const_expressions_context *context) @@ -3307,11 +3464,13 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args)) *args = add_function_defaults(*args, result_type, func_tuple, context); - newexpr = evaluate_function(funcid, result_type, result_typmod, *args, + newexpr = evaluate_function(funcid, result_type, result_typmod, + result_collid, input_collid, *args, func_tuple, context); if (!newexpr && allow_inline) - newexpr = inline_function(funcid, result_type, *args, + newexpr = inline_function(funcid, result_type, result_collid, + input_collid, *args, func_tuple, context); ReleaseSysCache(func_tuple); @@ -3558,7 +3717,8 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple) * simplify the function. */ static Expr * -evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, +evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, + Oid result_collid, Oid input_collid, List *args, HeapTuple func_tuple, eval_const_expressions_context *context) { @@ -3606,7 +3766,8 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, * function is not otherwise immutable. */ if (funcform->proisstrict && has_null_input) - return (Expr *) makeNullConst(result_type, result_typmod); + return (Expr *) makeNullConst(result_type, result_typmod, + result_collid); /* * Otherwise, can simplify only if all inputs are constants. (For a @@ -3640,10 +3801,13 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, newexpr->funcresulttype = result_type; newexpr->funcretset = false; newexpr->funcformat = COERCE_DONTCARE; /* doesn't matter */ + newexpr->funccollid = result_collid; /* doesn't matter */ + newexpr->inputcollid = input_collid; newexpr->args = args; newexpr->location = -1; - return evaluate_expr((Expr *) newexpr, result_type, result_typmod); + return evaluate_expr((Expr *) newexpr, result_type, result_typmod, + result_collid); } /* @@ -3668,16 +3832,20 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, * We must also beware of changing the volatility or strictness status of * functions by inlining them. * + * Also, at the moment we can't inline functions returning RECORD. This + * doesn't work in the general case because it discards information such + * as OUT-parameter declarations. + * * Returns a simplified expression if successful, or NULL if cannot * simplify the function. */ static Expr * -inline_function(Oid funcid, Oid result_type, List *args, +inline_function(Oid funcid, Oid result_type, Oid result_collid, + Oid input_collid, List *args, HeapTuple func_tuple, eval_const_expressions_context *context) { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); - Oid *argtypes; char *src; Datum tmp; bool isNull; @@ -3686,6 +3854,9 @@ inline_function(Oid funcid, Oid result_type, List *args, MemoryContext mycxt; inline_error_callback_arg callback_arg; ErrorContextCallback sqlerrcontext; + FuncExpr *fexpr; + SQLFunctionParseInfoPtr pinfo; + ParseState *pstate; List *raw_parsetree_list; Query *querytree; Node *newexpr; @@ -3700,6 +3871,7 @@ inline_function(Oid funcid, Oid result_type, List *args, if (funcform->prolang != SQLlanguageId || funcform->prosecdef || funcform->proretset || + funcform->prorettype == RECORDOID || !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || funcform->pronargs != list_length(args)) return NULL; @@ -3712,6 +3884,10 @@ inline_function(Oid funcid, Oid result_type, List *args, if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK) return NULL; + /* Check whether a plugin wants to hook function entry/exit */ + if (FmgrHookIsNeeded(funcid)) + return NULL; + /* * Make a temporary memory context, so that we don't leak all the stuff * that parsing might create. @@ -3744,17 +3920,25 @@ inline_function(Oid funcid, Oid result_type, List *args, sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; - /* Check for polymorphic arguments, and substitute actual arg types */ - argtypes = (Oid *) palloc(funcform->pronargs * sizeof(Oid)); - memcpy(argtypes, funcform->proargtypes.values, - funcform->pronargs * sizeof(Oid)); - for (i = 0; i < funcform->pronargs; i++) - { - if (IsPolymorphicType(argtypes[i])) - { - argtypes[i] = exprType((Node *) list_nth(args, i)); - } - } + /* + * Set up to handle parameters while parsing the function body. We need a + * dummy FuncExpr node containing the already-simplified arguments to pass + * to prepare_sql_fn_parse_info. (It is really only needed if there are + * some polymorphic arguments, but for simplicity we always build it.) + */ + fexpr = makeNode(FuncExpr); + fexpr->funcid = funcid; + fexpr->funcresulttype = result_type; + fexpr->funcretset = false; + fexpr->funcformat = COERCE_DONTCARE; /* doesn't matter */ + fexpr->funccollid = result_collid; /* doesn't matter */ + fexpr->inputcollid = input_collid; + fexpr->args = args; + fexpr->location = -1; + + pinfo = prepare_sql_fn_parse_info(func_tuple, + (Node *) fexpr, + input_collid); /* * We just do parsing and parse analysis, not rewriting, because rewriting @@ -3766,8 +3950,13 @@ inline_function(Oid funcid, Oid result_type, List *args, if (list_length(raw_parsetree_list) != 1) goto fail; - querytree = parse_analyze(linitial(raw_parsetree_list), src, - argtypes, funcform->pronargs); + pstate = make_parsestate(NULL); + pstate->p_sourcetext = src; + sql_fn_parser_setup(pstate, pinfo); + + querytree = transformStmt(pstate, linitial(raw_parsetree_list)); + + free_parsestate(pstate); /* * The single command must be a simple "SELECT expression". @@ -3900,6 +4089,28 @@ inline_function(Oid funcid, Oid result_type, List *args, MemoryContextDelete(mycxt); /* + * If the result is of a collatable type, force the result to expose the + * correct collation. In most cases this does not matter, but it's + * possible that the function result is used directly as a sort key or in + * other places where we expect exprCollation() to tell the truth. + */ + if (OidIsValid(result_collid)) + { + Oid exprcoll = exprCollation(newexpr); + + if (OidIsValid(exprcoll) && exprcoll != result_collid) + { + CollateExpr *newnode = makeNode(CollateExpr); + + newnode->arg = (Expr *) newexpr; + newnode->collOid = result_collid; + newnode->location = -1; + + newexpr = (Node *) newnode; + } + } + + /* * Since there is now no trace of the function in the plan tree, we must * explicitly record the plan's dependency on the function. */ @@ -3997,7 +4208,8 @@ sql_inline_error_callback(void *arg) * code and ensure we get the same result as the executor would get. */ static Expr * -evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod) +evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, + Oid result_collation) { EState *estate; ExprState *exprstate; @@ -4063,7 +4275,8 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod) /* * Make the constant result node. */ - return (Expr *) makeConst(result_type, result_typmod, resultTypLen, + return (Expr *) makeConst(result_type, result_typmod, result_collation, + resultTypLen, const_val, const_is_null, resultTypByVal); } @@ -4088,19 +4301,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) Oid func_oid; HeapTuple func_tuple; Form_pg_proc funcform; - Oid *argtypes; char *src; Datum tmp; bool isNull; bool modifyTargetList; MemoryContext oldcxt; MemoryContext mycxt; + List *saveInvalItems; inline_error_callback_arg callback_arg; ErrorContextCallback sqlerrcontext; + SQLFunctionParseInfoPtr pinfo; List *raw_parsetree_list; List *querytree_list; Query *querytree; - int i; Assert(rte->rtekind == RTE_FUNCTION); @@ -4143,6 +4356,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK) return NULL; + /* Check whether a plugin wants to hook function entry/exit */ + if (FmgrHookIsNeeded(func_oid)) + return NULL; + /* * OK, let's take a look at the function's pg_proc entry. */ @@ -4181,6 +4398,16 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(mycxt); + /* + * When we call eval_const_expressions below, it might try to add items to + * root->glob->invalItems. Since it is running in the temp context, those + * items will be in that context, and will need to be copied out if we're + * successful. Temporarily reset the list so that we can keep those items + * separate from the pre-existing list contents. + */ + saveInvalItems = root->glob->invalItems; + root->glob->invalItems = NIL; + /* Fetch the function body */ tmp = SysCacheGetAttr(PROCOID, func_tuple, @@ -4220,17 +4447,14 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) if (list_length(fexpr->args) != funcform->pronargs) goto fail; - /* Check for polymorphic arguments, and substitute actual arg types */ - argtypes = (Oid *) palloc(funcform->pronargs * sizeof(Oid)); - memcpy(argtypes, funcform->proargtypes.values, - funcform->pronargs * sizeof(Oid)); - for (i = 0; i < funcform->pronargs; i++) - { - if (IsPolymorphicType(argtypes[i])) - { - argtypes[i] = exprType((Node *) list_nth(fexpr->args, i)); - } - } + /* + * Set up to handle parameters while parsing the function body. We can + * use the FuncExpr just created as the input for + * prepare_sql_fn_parse_info. + */ + pinfo = prepare_sql_fn_parse_info(func_tuple, + (Node *) fexpr, + fexpr->inputcollid); /* * Parse, analyze, and rewrite (unlike inline_function(), we can't skip @@ -4241,8 +4465,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) if (list_length(raw_parsetree_list) != 1) goto fail; - querytree_list = pg_analyze_and_rewrite(linitial(raw_parsetree_list), src, - argtypes, funcform->pronargs); + querytree_list = pg_analyze_and_rewrite_params(linitial(raw_parsetree_list), + src, + (ParserSetupHook) sql_fn_parser_setup, + pinfo); if (list_length(querytree_list) != 1) goto fail; querytree = linitial(querytree_list); @@ -4307,11 +4533,20 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) querytree = copyObject(querytree); + /* copy up any new invalItems, too */ + root->glob->invalItems = list_concat(saveInvalItems, + copyObject(root->glob->invalItems)); + MemoryContextDelete(mycxt); error_context_stack = sqlerrcontext.previous; ReleaseSysCache(func_tuple); /* + * We don't have to fix collations here because the upper query is already + * parsed, ie, the collations in the RTE are what count. + */ + + /* * Since there is now no trace of the function in the plan tree, we must * explicitly record the plan's dependency on the function. */ @@ -4322,6 +4557,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) /* Here if func is not inlinable: release temp memory and return NULL */ fail: MemoryContextSwitchTo(oldcxt); + root->glob->invalItems = saveInvalItems; MemoryContextDelete(mycxt); error_context_stack = sqlerrcontext.previous; ReleaseSysCache(func_tuple); diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c index b03045d657..b618777762 100644 --- a/src/backend/optimizer/util/joininfo.c +++ b/src/backend/optimizer/util/joininfo.c @@ -3,12 +3,12 @@ * joininfo.c * joininfo list manipulation routines * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/joininfo.c,v 1.52 2010/01/02 16:57:48 momjian Exp $ + * src/backend/optimizer/util/joininfo.c * *------------------------------------------------------------------------- */ @@ -98,3 +98,37 @@ add_join_clause_to_rels(PlannerInfo *root, } bms_free(tmprelids); } + +/* + * remove_join_clause_from_rels + * Delete 'restrictinfo' from all the joininfo lists it is in + * + * This reverses the effect of add_join_clause_to_rels. It's used when we + * discover that a relation need not be joined at all. + * + * 'restrictinfo' describes the join clause + * 'join_relids' is the list of relations participating in the join clause + * (there must be more than one) + */ +void +remove_join_clause_from_rels(PlannerInfo *root, + RestrictInfo *restrictinfo, + Relids join_relids) +{ + Relids tmprelids; + int cur_relid; + + tmprelids = bms_copy(join_relids); + while ((cur_relid = bms_first_member(tmprelids)) >= 0) + { + RelOptInfo *rel = find_base_rel(root, cur_relid); + + /* + * Remove the restrictinfo from the list. Pointer comparison is + * sufficient. + */ + Assert(list_member_ptr(rel->joininfo, restrictinfo)); + rel->joininfo = list_delete_ptr(rel->joininfo, restrictinfo); + } + bms_free(tmprelids); +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index c54dafa932..4e7b456704 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3,12 +3,12 @@ * pathnode.c * Routines to manipulate pathlists and create path nodes * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.158 2010/03/28 22:59:33 tgl Exp $ + * src/backend/optimizer/util/pathnode.c * *------------------------------------------------------------------------- */ @@ -17,7 +17,9 @@ #include <math.h> #include "catalog/pg_operator.h" +#include "foreign/fdwapi.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -252,8 +254,9 @@ add_path(RelOptInfo *parent_rel, Path *new_path) { bool accept_new = true; /* unless we find a superior old path */ ListCell *insert_after = NULL; /* where to insert new item */ - ListCell *p1_prev = NULL; ListCell *p1; + ListCell *p1_prev; + ListCell *p1_next; /* * This is a convenient place to check for query cancel --- no part of the @@ -265,14 +268,19 @@ add_path(RelOptInfo *parent_rel, Path *new_path) * Loop to check proposed new path against old paths. Note it is possible * for more than one old path to be tossed out because new_path dominates * it. + * + * We can't use foreach here because the loop body may delete the current + * list cell. */ - p1 = list_head(parent_rel->pathlist); /* cannot use foreach here */ - while (p1 != NULL) + p1_prev = NULL; + for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next) { Path *old_path = (Path *) lfirst(p1); bool remove_old = false; /* unless new proves superior */ int costcmp; + p1_next = lnext(p1); + /* * As of Postgres 8.0, we use fuzzy cost comparison to avoid wasting * cycles keeping paths that are really not significantly different in @@ -341,20 +349,15 @@ add_path(RelOptInfo *parent_rel, Path *new_path) */ if (!IsA(old_path, IndexPath)) pfree(old_path); - /* Advance list pointer */ - if (p1_prev) - p1 = lnext(p1_prev); - else - p1 = list_head(parent_rel->pathlist); + /* p1_prev does not advance */ } else { /* new belongs after this old path if it has cost >= old's */ if (costcmp >= 0) insert_after = p1; - /* Advance list pointers */ + /* p1_prev advances */ p1_prev = p1; - p1 = lnext(p1); } /* @@ -413,6 +416,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel) * 'index' is a usable index. * 'clause_groups' is a list of lists of RestrictInfo nodes * to be used as index qual conditions in the scan. + * 'indexorderbys' is a list of bare expressions (no RestrictInfos) + * to be used as index ordering operators in the scan. * 'pathkeys' describes the ordering of the path. * 'indexscandir' is ForwardScanDirection or BackwardScanDirection * for an ordered index, or NoMovementScanDirection for @@ -426,6 +431,7 @@ IndexPath * create_index_path(PlannerInfo *root, IndexOptInfo *index, List *clause_groups, + List *indexorderbys, List *pathkeys, ScanDirection indexscandir, RelOptInfo *outer_rel) @@ -462,6 +468,7 @@ create_index_path(PlannerInfo *root, pathnode->indexinfo = index; pathnode->indexclauses = allclauses; pathnode->indexquals = indexquals; + pathnode->indexorderbys = indexorderbys; pathnode->isjoininner = (outer_rel != NULL); pathnode->indexscandir = indexscandir; @@ -503,7 +510,7 @@ create_index_path(PlannerInfo *root, pathnode->rows = rel->rows; } - cost_index(pathnode, root, index, indexquals, outer_rel); + cost_index(pathnode, root, index, indexquals, indexorderbys, outer_rel); return pathnode; } @@ -636,6 +643,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals) * create_append_path * Creates a path corresponding to an Append plan, returning the * pathnode. + * + * Note that we must handle subpaths = NIL, representing a dummy access path. */ AppendPath * create_append_path(RelOptInfo *rel, List *subpaths) @@ -649,6 +658,13 @@ create_append_path(RelOptInfo *rel, List *subpaths) * unsorted */ pathnode->subpaths = subpaths; + /* + * Compute cost as sum of subplan costs. We charge nothing extra for the + * Append itself, which perhaps is too optimistic, but since it doesn't do + * any selection or projection, it is a pretty cheap node. + * + * If you change this, see also make_append(). + */ pathnode->path.startup_cost = 0; pathnode->path.total_cost = 0; foreach(l, subpaths) @@ -664,6 +680,97 @@ create_append_path(RelOptInfo *rel, List *subpaths) } /* + * create_merge_append_path + * Creates a path corresponding to a MergeAppend plan, returning the + * pathnode. + */ +MergeAppendPath * +create_merge_append_path(PlannerInfo *root, + RelOptInfo *rel, + List *subpaths, + List *pathkeys) +{ + MergeAppendPath *pathnode = makeNode(MergeAppendPath); + Cost input_startup_cost; + Cost input_total_cost; + ListCell *l; + + pathnode->path.pathtype = T_MergeAppend; + pathnode->path.parent = rel; + pathnode->path.pathkeys = pathkeys; + pathnode->subpaths = subpaths; + + /* + * Apply query-wide LIMIT if known and path is for sole base relation. + * Finding out the latter at this low level is a bit klugy. + */ + pathnode->limit_tuples = root->limit_tuples; + if (pathnode->limit_tuples >= 0) + { + Index rti; + + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + if (brel == NULL) + continue; + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + if (brel != rel) + { + /* Oops, it's a join query */ + pathnode->limit_tuples = -1.0; + break; + } + } + } + + /* Add up all the costs of the input paths */ + input_startup_cost = 0; + input_total_cost = 0; + foreach(l, subpaths) + { + Path *subpath = (Path *) lfirst(l); + + if (pathkeys_contained_in(pathkeys, subpath->pathkeys)) + { + /* Subpath is adequately ordered, we won't need to sort it */ + input_startup_cost += subpath->startup_cost; + input_total_cost += subpath->total_cost; + } + else + { + /* We'll need to insert a Sort node, so include cost for that */ + Path sort_path; /* dummy for result of cost_sort */ + + cost_sort(&sort_path, + root, + pathkeys, + subpath->total_cost, + subpath->parent->tuples, + subpath->parent->width, + 0.0, + work_mem, + pathnode->limit_tuples); + input_startup_cost += sort_path.startup_cost; + input_total_cost += sort_path.total_cost; + } + } + + /* Now we can compute total costs of the MergeAppend */ + cost_merge_append(&pathnode->path, root, + pathkeys, list_length(subpaths), + input_startup_cost, input_total_cost, + rel->tuples); + + return pathnode; +} + +/* * create_result_path * Creates a path representing a Result-and-nothing-else plan. * This is only used for the case of a query with an empty jointree. @@ -807,6 +914,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, Relids left_varnos; Relids right_varnos; Relids all_varnos; + Oid opinputtype; /* Is it a binary opclause? */ if (!IsA(op, OpExpr) || @@ -837,6 +945,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, left_varnos = pull_varnos(left_expr); right_varnos = pull_varnos(right_expr); all_varnos = bms_union(left_varnos, right_varnos); + opinputtype = exprType(left_expr); /* Does it reference both sides? */ if (!bms_overlap(all_varnos, sjinfo->syn_righthand) || @@ -875,14 +984,14 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, if (all_btree) { /* oprcanmerge is considered a hint... */ - if (!op_mergejoinable(opno) || + if (!op_mergejoinable(opno, opinputtype) || get_mergejoin_opfamilies(opno) == NIL) all_btree = false; } if (all_hash) { /* ... but oprcanhash had better be correct */ - if (!op_hashjoinable(opno)) + if (!op_hashjoinable(opno, opinputtype)) all_hash = false; } if (!(all_btree || all_hash)) @@ -969,6 +1078,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, subpath->total_cost, rel->rows, rel->width, + 0.0, + work_mem, -1.0); /* @@ -992,7 +1103,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, all_hash = false; /* don't try to hash */ else cost_agg(&agg_path, root, - AGG_HASHED, 0, + AGG_HASHED, NULL, numCols, pathnode->rows, subpath->startup_cost, subpath->total_cost, @@ -1341,6 +1452,41 @@ create_remotequery_path(PlannerInfo *root, RelOptInfo *rel) #endif /* + * create_foreignscan_path + * Creates a path corresponding to a scan of a foreign table, + * returning the pathnode. + */ +ForeignPath * +create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel) +{ + ForeignPath *pathnode = makeNode(ForeignPath); + RangeTblEntry *rte; + FdwRoutine *fdwroutine; + FdwPlan *fdwplan; + + pathnode->path.pathtype = T_ForeignScan; + pathnode->path.parent = rel; + pathnode->path.pathkeys = NIL; /* result is always unordered */ + + /* Get FDW's callback info */ + rte = planner_rt_fetch(rel->relid, root); + fdwroutine = GetFdwRoutineByRelId(rte->relid); + + /* Let the FDW do its planning */ + fdwplan = fdwroutine->PlanForeignScan(rte->relid, root, rel); + if (fdwplan == NULL || !IsA(fdwplan, FdwPlan)) + elog(ERROR, "foreign-data wrapper PlanForeignScan function for relation %u did not return an FdwPlan struct", + rte->relid); + pathnode->fdwplan = fdwplan; + + /* use costs estimated by FDW */ + pathnode->path.startup_cost = fdwplan->startup_cost; + pathnode->path.total_cost = fdwplan->total_cost; + + return pathnode; +} + +/* * create_nestloop_path * Creates a pathnode corresponding to a nestloop join between two * relations. diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index 837a0c64b3..9796fbf9b6 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -4,12 +4,12 @@ * PlaceHolderVar and PlaceHolderInfo manipulation routines * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/placeholder.c,v 1.8 2010/07/06 19:18:57 momjian Exp $ + * src/backend/optimizer/util/placeholder.c * *------------------------------------------------------------------------- */ @@ -22,6 +22,11 @@ #include "optimizer/var.h" #include "utils/lsyscache.h" +/* Local functions */ +static Relids find_placeholders_recurse(PlannerInfo *root, Node *jtnode); +static void find_placeholders_in_qual(PlannerInfo *root, Node *qual, + Relids relids); + /* * make_placeholder_expr @@ -47,6 +52,12 @@ make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels) * find_placeholder_info * Fetch the PlaceHolderInfo for the given PHV; create it if not found * + * This is separate from make_placeholder_expr because subquery pullup has + * to make PlaceHolderVars for expressions that might not be used at all in + * the upper query, or might not remain after const-expression simplification. + * We build PlaceHolderInfos only for PHVs that are still present in the + * simplified query passed to query_planner(). + * * Note: this should only be called after query_planner() has started. */ PlaceHolderInfo * @@ -71,8 +82,9 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv) phinfo->phid = phv->phid; phinfo->ph_var = copyObject(phv); phinfo->ph_eval_at = pull_varnos((Node *) phv); - /* ph_eval_at may change later, see fix_placeholder_eval_levels */ + /* ph_eval_at may change later, see update_placeholder_eval_levels */ phinfo->ph_needed = NULL; /* initially it's unused */ + phinfo->ph_may_need = NULL; /* for the moment, estimate width using just the datatype info */ phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr), exprTypmod((Node *) phv->phexpr)); @@ -83,21 +95,168 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv) } /* - * fix_placeholder_eval_levels + * find_placeholders_in_jointree + * Search the jointree for PlaceHolderVars, and build PlaceHolderInfos + * + * We don't need to look at the targetlist because build_base_rel_tlists() + * will already have made entries for any PHVs in the tlist. + */ +void +find_placeholders_in_jointree(PlannerInfo *root) +{ + /* We need do nothing if the query contains no PlaceHolderVars */ + if (root->glob->lastPHId != 0) + { + /* Start recursion at top of jointree */ + Assert(root->parse->jointree != NULL && + IsA(root->parse->jointree, FromExpr)); + (void) find_placeholders_recurse(root, (Node *) root->parse->jointree); + } +} + +/* + * find_placeholders_recurse + * One recursion level of find_placeholders_in_jointree. + * + * jtnode is the current jointree node to examine. + * + * The result is the set of base Relids contained in or below jtnode. + * This is just an internal convenience, it's not used at the top level. + */ +static Relids +find_placeholders_recurse(PlannerInfo *root, Node *jtnode) +{ + Relids jtrelids; + + if (jtnode == NULL) + return NULL; + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + + /* No quals to deal with, just return correct result */ + jtrelids = bms_make_singleton(varno); + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + /* + * First, recurse to handle child joins, and form their relid set. + */ + jtrelids = NULL; + foreach(l, f->fromlist) + { + Relids sub_relids; + + sub_relids = find_placeholders_recurse(root, lfirst(l)); + jtrelids = bms_join(jtrelids, sub_relids); + } + + /* + * Now process the top-level quals. + */ + find_placeholders_in_qual(root, f->quals, jtrelids); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + Relids leftids, + rightids; + + /* + * First, recurse to handle child joins, and form their relid set. + */ + leftids = find_placeholders_recurse(root, j->larg); + rightids = find_placeholders_recurse(root, j->rarg); + jtrelids = bms_join(leftids, rightids); + + /* Process the qual clauses */ + find_placeholders_in_qual(root, j->quals, jtrelids); + } + else + { + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); + jtrelids = NULL; /* keep compiler quiet */ + } + return jtrelids; +} + +/* + * find_placeholders_in_qual + * Process a qual clause for find_placeholders_in_jointree. + * + * relids is the syntactic join level to mark as the "maybe needed" level + * for each PlaceHolderVar found in the qual clause. + */ +static void +find_placeholders_in_qual(PlannerInfo *root, Node *qual, Relids relids) +{ + List *vars; + ListCell *vl; + + /* + * pull_var_clause does more than we need here, but it'll do and it's + * convenient to use. + */ + vars = pull_var_clause(qual, PVC_INCLUDE_PLACEHOLDERS); + foreach(vl, vars) + { + PlaceHolderVar *phv = (PlaceHolderVar *) lfirst(vl); + PlaceHolderInfo *phinfo; + + /* Ignore any plain Vars */ + if (!IsA(phv, PlaceHolderVar)) + continue; + + /* Create a PlaceHolderInfo entry if there's not one already */ + phinfo = find_placeholder_info(root, phv); + + /* Mark the PHV as possibly needed at the qual's syntactic level */ + phinfo->ph_may_need = bms_add_members(phinfo->ph_may_need, relids); + + /* + * This is a bit tricky: the PHV's contained expression may contain + * other, lower-level PHVs. We need to get those into the + * PlaceHolderInfo list, but they aren't going to be needed where the + * outer PHV is referenced. Rather, they'll be needed where the outer + * PHV is evaluated. We can estimate that (conservatively) as the + * syntactic location of the PHV's expression. Recurse to take care + * of any such PHVs. + */ + find_placeholders_in_qual(root, (Node *) phv->phexpr, phv->phrels); + } + list_free(vars); +} + +/* + * update_placeholder_eval_levels * Adjust the target evaluation levels for placeholders * * The initial eval_at level set by find_placeholder_info was the set of - * rels used in the placeholder's expression (or the whole subselect if - * the expr is variable-free). If the subselect contains any outer joins - * that can null any of those rels, we must delay evaluation to above those - * joins. + * rels used in the placeholder's expression (or the whole subselect below + * the placeholder's syntactic location, if the expr is variable-free). + * If the subselect contains any outer joins that can null any of those rels, + * we must delay evaluation to above those joins. + * + * We repeat this operation each time we add another outer join to + * root->join_info_list. It's somewhat annoying to have to do that, but + * since we don't have very much information on the placeholders' locations, + * it's hard to avoid. Each placeholder's eval_at level must be correct + * by the time it starts to figure in outer-join delay decisions for higher + * outer joins. * * In future we might want to put additional policy/heuristics here to * try to determine an optimal evaluation level. The current rules will - * result in evaluation at the lowest possible level. + * result in evaluation at the lowest possible level. However, pushing a + * placeholder eval up the tree is likely to further constrain evaluation + * order for outer joins, so it could easily be counterproductive; and we + * don't have enough information at this point to make an intelligent choice. */ void -fix_placeholder_eval_levels(PlannerInfo *root) +update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo) { ListCell *lc1; @@ -105,16 +264,27 @@ fix_placeholder_eval_levels(PlannerInfo *root) { PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc1); Relids syn_level = phinfo->ph_var->phrels; - Relids eval_at = phinfo->ph_eval_at; + Relids eval_at; bool found_some; ListCell *lc2; /* + * We don't need to do any work on this placeholder unless the + * newly-added outer join is syntactically beneath its location. + */ + if (!bms_is_subset(new_sjinfo->syn_lefthand, syn_level) || + !bms_is_subset(new_sjinfo->syn_righthand, syn_level)) + continue; + + /* * Check for delays due to lower outer joins. This is the same logic * as in check_outerjoin_delay in initsplan.c, except that we don't - * want to modify the delay_upper_joins flags; that was all handled - * already during distribute_qual_to_rels. + * have anything to do with the delay_upper_joins flags; delay of + * upper outer joins will be handled later, based on the eval_at + * values we compute now. */ + eval_at = phinfo->ph_eval_at; + do { found_some = false; @@ -122,7 +292,7 @@ fix_placeholder_eval_levels(PlannerInfo *root) { SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc2); - /* disregard joins not within the expr's sub-select */ + /* disregard joins not within the PHV's sub-select */ if (!bms_is_subset(sjinfo->syn_lefthand, syn_level) || !bms_is_subset(sjinfo->syn_righthand, syn_level)) continue; @@ -149,16 +319,32 @@ fix_placeholder_eval_levels(PlannerInfo *root) } while (found_some); phinfo->ph_eval_at = eval_at; + } +} - /* - * Now that we know where to evaluate the placeholder, make sure that - * any vars or placeholders it uses will be available at that join - * level. NOTE: this could cause more PlaceHolderInfos to be added to - * placeholder_list. That is okay because we'll process them before - * falling out of the foreach loop. Also, it could cause the - * ph_needed sets of existing list entries to expand, which is also - * okay because this loop doesn't examine those. - */ +/* + * fix_placeholder_input_needed_levels + * Adjust the "needed at" levels for placeholder inputs + * + * This is called after we've finished determining the eval_at levels for + * all placeholders. We need to make sure that all vars and placeholders + * needed to evaluate each placeholder will be available at the join level + * where the evaluation will be done. Note that this loop can have + * side-effects on the ph_needed sets of other PlaceHolderInfos; that's okay + * because we don't examine ph_needed here, so there are no ordering issues + * to worry about. + */ +void +fix_placeholder_input_needed_levels(PlannerInfo *root) +{ + ListCell *lc; + + foreach(lc, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); + Relids eval_at = phinfo->ph_eval_at; + + /* No work unless it'll be evaluated above baserel level */ if (bms_membership(eval_at) == BMS_MULTIPLE) { List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr, @@ -175,11 +361,11 @@ fix_placeholder_eval_levels(PlannerInfo *root) * Add any required PlaceHolderVars to base rels' targetlists. * * If any placeholder can be computed at a base rel and is needed above it, - * add it to that rel's targetlist. We have to do this separately from - * fix_placeholder_eval_levels() because join removal happens in between, - * and can change the ph_eval_at sets. There is essentially the same logic - * in add_placeholders_to_joinrel, but we can't do that part until joinrels - * are formed. + * add it to that rel's targetlist. This might look like it could be merged + * with fix_placeholder_input_needed_levels, but it must be separate because + * join removal happens in between, and can change the ph_eval_at sets. There + * is essentially the same logic in add_placeholders_to_joinrel, but we can't + * do that part until joinrels are formed. */ void add_placeholders_to_base_rels(PlannerInfo *root) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 10800b488f..b28681630b 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -4,12 +4,12 @@ * routines for accessing the system catalogs * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.163 2010/03/30 21:58:10 tgl Exp $ + * src/backend/optimizer/util/plancat.c * *------------------------------------------------------------------------- */ @@ -46,6 +46,7 @@ int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION; get_relation_info_hook_type get_relation_info_hook = NULL; +static int32 get_rel_data_width(Relation rel, int32 *attr_widths); static List *get_relation_constraints(PlannerInfo *root, Oid relationObjectId, RelOptInfo *rel, bool include_notnull); @@ -89,6 +90,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, */ relation = heap_open(relationObjectId, NoLock); + /* Temporary and unlogged relations are inaccessible during recovery. */ + if (!RelationNeedsWAL(relation) && RecoveryInProgress()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary or unlogged relations during recovery"))); + rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1; rel->max_attr = RelationGetNumberOfAttributes(relation); rel->reltablespace = RelationGetForm(relation)->reltablespace; @@ -188,78 +195,112 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelationGetForm(indexRelation)->reltablespace; info->rel = rel; info->ncolumns = ncolumns = index->indnatts; - - /* - * Allocate per-column info arrays. To save a few palloc cycles - * we allocate all the Oid-type arrays in one request. Note that - * the opfamily array needs an extra, terminating zero at the end. - * We pre-zero the ordering info in case the index is unordered. - */ info->indexkeys = (int *) palloc(sizeof(int) * ncolumns); - info->opfamily = (Oid *) palloc0(sizeof(Oid) * (4 * ncolumns + 1)); - info->opcintype = info->opfamily + (ncolumns + 1); - info->fwdsortop = info->opcintype + ncolumns; - info->revsortop = info->fwdsortop + ncolumns; - info->nulls_first = (bool *) palloc0(sizeof(bool) * ncolumns); + info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); + info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); + info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); for (i = 0; i < ncolumns; i++) { info->indexkeys[i] = index->indkey.values[i]; + info->indexcollations[i] = indexRelation->rd_indcollation[i]; info->opfamily[i] = indexRelation->rd_opfamily[i]; info->opcintype[i] = indexRelation->rd_opcintype[i]; } info->relam = indexRelation->rd_rel->relam; info->amcostestimate = indexRelation->rd_am->amcostestimate; + info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop; info->amoptionalkey = indexRelation->rd_am->amoptionalkey; info->amsearchnulls = indexRelation->rd_am->amsearchnulls; info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple); info->amhasgetbitmap = OidIsValid(indexRelation->rd_am->amgetbitmap); /* - * Fetch the ordering operators associated with the index, if any. - * We expect that all ordering-capable indexes use btree's - * strategy numbers for the ordering operators. + * Fetch the ordering information for the index, if any. */ - if (indexRelation->rd_am->amcanorder) + if (info->relam == BTREE_AM_OID) { - int nstrat = indexRelation->rd_am->amstrategies; + /* + * If it's a btree index, we can use its opfamily OIDs + * directly as the sort ordering opfamily OIDs. + */ + Assert(indexRelation->rd_am->amcanorder); + + info->sortopfamily = info->opfamily; + info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); + info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; i++) { int16 opt = indexRelation->rd_indoption[i]; - int fwdstrat; - int revstrat; - if (opt & INDOPTION_DESC) - { - fwdstrat = BTGreaterStrategyNumber; - revstrat = BTLessStrategyNumber; - } - else - { - fwdstrat = BTLessStrategyNumber; - revstrat = BTGreaterStrategyNumber; - } + info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; + info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; + } + } + else if (indexRelation->rd_am->amcanorder) + { + /* + * Otherwise, identify the corresponding btree opfamilies by + * trying to map this index's "<" operators into btree. Since + * "<" uniquely defines the behavior of a sort order, this is + * a sufficient test. + * + * XXX This method is rather slow and also requires the + * undesirable assumption that the other index AM numbers its + * strategies the same as btree. It'd be better to have a way + * to explicitly declare the corresponding btree opfamily for + * each opfamily of the other index type. But given the lack + * of current or foreseeable amcanorder index types, it's not + * worth expending more effort on now. + */ + info->sortopfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); + info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); + info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); + + for (i = 0; i < ncolumns; i++) + { + int16 opt = indexRelation->rd_indoption[i]; + Oid ltopr; + Oid btopfamily; + Oid btopcintype; + int16 btstrategy; - /* - * Index AM must have a fixed set of strategies for it to - * make sense to specify amcanorder, so we need not allow - * the case amstrategies == 0. - */ - if (fwdstrat > 0) + info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; + info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; + + ltopr = get_opfamily_member(info->opfamily[i], + info->opcintype[i], + info->opcintype[i], + BTLessStrategyNumber); + if (OidIsValid(ltopr) && + get_ordering_op_properties(ltopr, + &btopfamily, + &btopcintype, + &btstrategy) && + btopcintype == info->opcintype[i] && + btstrategy == BTLessStrategyNumber) { - Assert(fwdstrat <= nstrat); - info->fwdsortop[i] = indexRelation->rd_operator[i * nstrat + fwdstrat - 1]; + /* Successful mapping */ + info->sortopfamily[i] = btopfamily; } - if (revstrat > 0) + else { - Assert(revstrat <= nstrat); - info->revsortop[i] = indexRelation->rd_operator[i * nstrat + revstrat - 1]; + /* Fail ... quietly treat index as unordered */ + info->sortopfamily = NULL; + info->reverse_sort = NULL; + info->nulls_first = NULL; + break; } - info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; } } + else + { + info->sortopfamily = NULL; + info->reverse_sort = NULL; + info->nulls_first = NULL; + } /* * Fetch the index expressions and predicate, if any. We must @@ -275,6 +316,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, ChangeVarNodes((Node *) info->indpred, 1, varno, 0); info->predOK = false; /* set later in indxpath.c */ info->unique = index->indisunique; + info->hypothetical = false; /* * Estimate the index size. If it's not a partial index, we lock @@ -321,7 +363,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * estimate_rel_size - estimate # pages and # tuples in a table or index * * If attr_widths isn't NULL, it points to the zero-index entry of the - * relation's attr_width[] cache; we fill this in if we have need to compute + * relation's attr_widths[] cache; we fill this in if we have need to compute * the attribute widths for estimation purposes. */ void @@ -406,28 +448,9 @@ estimate_rel_size(Relation rel, int32 *attr_widths, * platform dependencies in the default plans which are kind * of a headache for regression testing. */ - int32 tuple_width = 0; - int i; + int32 tuple_width; - for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++) - { - Form_pg_attribute att = rel->rd_att->attrs[i - 1]; - int32 item_width; - - if (att->attisdropped) - continue; - /* This should match set_rel_width() in costsize.c */ - item_width = get_attavgwidth(RelationGetRelid(rel), i); - if (item_width <= 0) - { - item_width = get_typavgwidth(att->atttypid, - att->atttypmod); - Assert(item_width > 0); - } - if (attr_widths != NULL) - attr_widths[i] = item_width; - tuple_width += item_width; - } + tuple_width = get_rel_data_width(rel, attr_widths); tuple_width += sizeof(HeapTupleHeaderData); tuple_width += sizeof(ItemPointerData); /* note: integer division is intentional here */ @@ -440,6 +463,11 @@ estimate_rel_size(Relation rel, int32 *attr_widths, *pages = 1; *tuples = 1; break; + case RELKIND_FOREIGN_TABLE: + /* Just use whatever's in pg_class */ + *pages = rel->rd_rel->relpages; + *tuples = rel->rd_rel->reltuples; + break; default: /* else it has no disk storage; probably shouldn't get here? */ *pages = 0; @@ -450,6 +478,78 @@ estimate_rel_size(Relation rel, int32 *attr_widths, /* + * get_rel_data_width + * + * Estimate the average width of (the data part of) the relation's tuples. + * + * If attr_widths isn't NULL, it points to the zero-index entry of the + * relation's attr_widths[] cache; use and update that cache as appropriate. + * + * Currently we ignore dropped columns. Ideally those should be included + * in the result, but we haven't got any way to get info about them; and + * since they might be mostly NULLs, treating them as zero-width is not + * necessarily the wrong thing anyway. + */ +static int32 +get_rel_data_width(Relation rel, int32 *attr_widths) +{ + int32 tuple_width = 0; + int i; + + for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++) + { + Form_pg_attribute att = rel->rd_att->attrs[i - 1]; + int32 item_width; + + if (att->attisdropped) + continue; + + /* use previously cached data, if any */ + if (attr_widths != NULL && attr_widths[i] > 0) + { + tuple_width += attr_widths[i]; + continue; + } + + /* This should match set_rel_width() in costsize.c */ + item_width = get_attavgwidth(RelationGetRelid(rel), i); + if (item_width <= 0) + { + item_width = get_typavgwidth(att->atttypid, att->atttypmod); + Assert(item_width > 0); + } + if (attr_widths != NULL) + attr_widths[i] = item_width; + tuple_width += item_width; + } + + return tuple_width; +} + +/* + * get_relation_data_width + * + * External API for get_rel_data_width: same behavior except we have to + * open the relcache entry. + */ +int32 +get_relation_data_width(Oid relid, int32 *attr_widths) +{ + int32 result; + Relation relation; + + /* As above, assume relation is already locked */ + relation = heap_open(relid, NoLock); + + result = get_rel_data_width(relation, attr_widths); + + heap_close(relation, NoLock); + + return result; +} + + +/* * get_relation_constraints * * Retrieve the CHECK constraint expressions of the given relation. @@ -542,6 +642,7 @@ get_relation_constraints(PlannerInfo *root, i, att->atttypid, att->atttypmod, + att->attcollation, 0); ntest->nulltesttype = IS_NOT_NULL; ntest->argisrow = type_is_rowtype(att->atttypid); @@ -705,6 +806,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) attrno, att_tup->atttypid, att_tup->atttypmod, + att_tup->attcollation, 0); tlist = lappend(tlist, @@ -727,11 +829,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) * A resjunk column of the subquery can be reflected as * resjunk in the physical tlist; we need not punt. */ - var = makeVar(varno, - tle->resno, - exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr), - 0); + var = makeVarFromTargetEntry(varno, tle); tlist = lappend(tlist, makeTargetEntry((Expr *) var, diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c index 66d3a7498f..a7e83729b1 100644 --- a/src/backend/optimizer/util/predtest.c +++ b/src/backend/optimizer/util/predtest.c @@ -4,12 +4,12 @@ * Routines to attempt to prove logical implications between predicate * expressions. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.33 2010/02/26 02:00:47 momjian Exp $ + * src/backend/optimizer/util/predtest.c * *------------------------------------------------------------------------- */ @@ -906,12 +906,15 @@ arrayconst_startup_fn(Node *clause, PredIterInfo info) state->opexpr.opfuncid = saop->opfuncid; state->opexpr.opresulttype = BOOLOID; state->opexpr.opretset = false; + state->opexpr.opcollid = InvalidOid; + state->opexpr.inputcollid = saop->inputcollid; state->opexpr.args = list_copy(saop->args); /* Set up a dummy Const node to hold the per-element values */ state->constexpr.xpr.type = T_Const; state->constexpr.consttype = ARR_ELEMTYPE(arrayval); state->constexpr.consttypmod = -1; + state->constexpr.constcollid = arrayconst->constcollid; state->constexpr.constlen = elmlen; state->constexpr.constbyval = elmbyval; lsecond(state->opexpr.args) = &state->constexpr; @@ -971,6 +974,8 @@ arrayexpr_startup_fn(Node *clause, PredIterInfo info) state->opexpr.opfuncid = saop->opfuncid; state->opexpr.opresulttype = BOOLOID; state->opexpr.opretset = false; + state->opexpr.opcollid = InvalidOid; + state->opexpr.inputcollid = saop->inputcollid; state->opexpr.args = list_copy(saop->args); /* Initialize iteration variable to first member of ArrayExpr */ @@ -1344,6 +1349,8 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) *clause_const; bool pred_var_on_left, clause_var_on_left; + Oid pred_collation, + clause_collation; Oid pred_op, clause_op, test_op; @@ -1420,6 +1427,14 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) return false; /* + * They'd better have the same collation, too. + */ + pred_collation = ((OpExpr *) predicate)->inputcollid; + clause_collation = ((OpExpr *) clause)->inputcollid; + if (pred_collation != clause_collation) + return false; + + /* * Okay, get the operators in the two clauses we're comparing. Commute * them if needed so that we can assume the variables are on the left. */ @@ -1464,7 +1479,9 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) BOOLOID, false, (Expr *) pred_const, - (Expr *) clause_const); + (Expr *) clause_const, + InvalidOid, + pred_collation); /* Fill in opfuncids */ fix_opfuncids((Node *) test_expr); @@ -1661,8 +1678,9 @@ get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it) * From the same opfamily, find a strategy number for the clause_op, * if possible */ - clause_tuple = SearchSysCache2(AMOPOPID, + clause_tuple = SearchSysCache3(AMOPOPID, ObjectIdGetDatum(clause_op), + CharGetDatum(AMOP_SEARCH), ObjectIdGetDatum(opfamily_id)); if (HeapTupleIsValid(clause_tuple)) { @@ -1677,8 +1695,9 @@ get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it) } else if (OidIsValid(clause_op_negator)) { - clause_tuple = SearchSysCache2(AMOPOPID, + clause_tuple = SearchSysCache3(AMOPOPID, ObjectIdGetDatum(clause_op_negator), + CharGetDatum(AMOP_SEARCH), ObjectIdGetDatum(opfamily_id)); if (HeapTupleIsValid(clause_tuple)) { diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index efdebf2def..614720b10b 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -3,12 +3,12 @@ * relnode.c * Relation-node lookup/construction routines * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.98 2010/02/26 02:00:47 momjian Exp $ + * src/backend/optimizer/util/relnode.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index caa3cd77c0..93f9aa846a 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -3,12 +3,12 @@ * restrictinfo.c * RestrictInfo node manipulation routines. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.63 2010/02/26 02:00:49 momjian Exp $ + * src/backend/optimizer/util/restrictinfo.c * *------------------------------------------------------------------------- */ diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 37ac8aaea2..d17424e40f 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -3,12 +3,12 @@ * tlist.c * Target list manipulation routines * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.87 2010/01/02 16:57:48 momjian Exp $ + * src/backend/optimizer/util/tlist.c * *------------------------------------------------------------------------- */ @@ -18,7 +18,6 @@ #include "nodes/nodeFuncs.h" #include "optimizer/tlist.h" #include "optimizer/var.h" -#include "utils/lsyscache.h" /***************************************************************************** @@ -196,6 +195,40 @@ tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK) return true; } +/* + * Does tlist have same exposed collations as listed in colCollations? + * + * Identical logic to the above, but for collations. + */ +bool +tlist_same_collations(List *tlist, List *colCollations, bool junkOK) +{ + ListCell *l; + ListCell *curColColl = list_head(colCollations); + + foreach(l, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + { + if (!junkOK) + return false; + } + else + { + if (curColColl == NULL) + return false; /* tlist longer than colCollations */ + if (exprCollation((Node *) tle->expr) != lfirst_oid(curColColl)) + return false; + curColColl = lnext(curColColl); + } + } + if (curColColl != NULL) + return false; /* tlist shorter than colCollations */ + return true; +} + /* * get_sortgroupref_tle @@ -348,10 +381,7 @@ grouping_is_sortable(List *groupClause) /* * grouping_is_hashable - is it possible to implement grouping list by hashing? * - * We assume hashing is OK if the equality operators are marked oprcanhash. - * (If there isn't actually a supporting hash function, the executor will - * complain at runtime; but this is a misdeclaration of the operator, not - * a system bug.) + * We rely on the parser to have set the hashable flag correctly. */ bool grouping_is_hashable(List *groupClause) @@ -362,7 +392,7 @@ grouping_is_hashable(List *groupClause) { SortGroupClause *groupcl = (SortGroupClause *) lfirst(glitem); - if (!op_hashjoinable(groupcl->eqop)) + if (!groupcl->hashable) return false; } return true; diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 39ec4ea8ef..3bc7970edf 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -9,12 +9,12 @@ * contains variables. * * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.88 2010/07/08 00:14:03 tgl Exp $ + * src/backend/optimizer/util/var.c * *------------------------------------------------------------------------- */ @@ -754,7 +754,7 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context) * entries might now be arbitrary expressions, not just Vars. This affects * this function in one important way: we might find ourselves inserting * SubLink expressions into subqueries, and we must make sure that their - * Query.hasSubLinks fields get set to TRUE if so. If there are any + * Query.hasSubLinks fields get set to TRUE if so. If there are any * SubLinks in the join alias lists, the outer Query should already have * hasSubLinks = TRUE, so this is only relevant to un-flattened subqueries. * @@ -898,6 +898,7 @@ flatten_join_alias_vars_mutator(Node *node, /* Shouldn't need to handle these planner auxiliary nodes here */ Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); return expression_tree_mutator(node, flatten_join_alias_vars_mutator, (void *) context); |
