summaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
authorMichael P2011-07-05 03:16:11 +0000
committerMichael P2011-07-06 03:40:35 +0000
commit0bbfc1e6338b5d98d6cb83fa75f2c38f527d4d4b (patch)
tree46fa412a31d08ea6e53d488ae7bc231df0b273da /src/backend/optimizer
parent091b0e828cf0fd5bbd1f9ae58ab96fc983e55d77 (diff)
parenta4bebdd92624e018108c2610fc3f2c1584b6c687 (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')
-rw-r--r--src/backend/optimizer/Makefile2
-rw-r--r--src/backend/optimizer/README45
-rw-r--r--src/backend/optimizer/geqo/Makefile2
-rw-r--r--src/backend/optimizer/geqo/geqo_copy.c4
-rw-r--r--src/backend/optimizer/geqo/geqo_cx.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_erx.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_eval.c4
-rw-r--r--src/backend/optimizer/geqo/geqo_main.c16
-rw-r--r--src/backend/optimizer/geqo/geqo_misc.c4
-rw-r--r--src/backend/optimizer/geqo/geqo_mutation.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_ox1.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_ox2.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_pmx.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_pool.c4
-rw-r--r--src/backend/optimizer/geqo/geqo_px.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_random.c4
-rw-r--r--src/backend/optimizer/geqo/geqo_recombination.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_selection.c4
-rw-r--r--src/backend/optimizer/path/Makefile2
-rw-r--r--src/backend/optimizer/path/allpaths.c264
-rw-r--r--src/backend/optimizer/path/clausesel.c4
-rw-r--r--src/backend/optimizer/path/costsize.c448
-rw-r--r--src/backend/optimizer/path/equivclass.c215
-rw-r--r--src/backend/optimizer/path/indxpath.c506
-rw-r--r--src/backend/optimizer/path/joinpath.c142
-rw-r--r--src/backend/optimizer/path/joinrels.c80
-rw-r--r--src/backend/optimizer/path/orindxpath.c4
-rw-r--r--src/backend/optimizer/path/pathkeys.c427
-rw-r--r--src/backend/optimizer/path/tidpath.c4
-rw-r--r--src/backend/optimizer/plan/Makefile2
-rw-r--r--src/backend/optimizer/plan/README44
-rw-r--r--src/backend/optimizer/plan/analyzejoins.c110
-rw-r--r--src/backend/optimizer/plan/createplan.c715
-rw-r--r--src/backend/optimizer/plan/initsplan.c93
-rw-r--r--src/backend/optimizer/plan/planagg.c818
-rw-r--r--src/backend/optimizer/plan/planmain.c92
-rw-r--r--src/backend/optimizer/plan/planner.c277
-rw-r--r--src/backend/optimizer/plan/setrefs.c308
-rw-r--r--src/backend/optimizer/plan/subselect.c323
-rw-r--r--src/backend/optimizer/prep/Makefile2
-rw-r--r--src/backend/optimizer/prep/prepjointree.c187
-rw-r--r--src/backend/optimizer/prep/prepqual.c383
-rw-r--r--src/backend/optimizer/prep/preptlist.c92
-rw-r--r--src/backend/optimizer/prep/prepunion.c170
-rw-r--r--src/backend/optimizer/util/Makefile2
-rw-r--r--src/backend/optimizer/util/clauses.c520
-rw-r--r--src/backend/optimizer/util/joininfo.c38
-rw-r--r--src/backend/optimizer/util/pathnode.c178
-rw-r--r--src/backend/optimizer/util/placeholder.c242
-rw-r--r--src/backend/optimizer/util/plancat.c238
-rw-r--r--src/backend/optimizer/util/predtest.c29
-rw-r--r--src/backend/optimizer/util/relnode.c4
-rw-r--r--src/backend/optimizer/util/restrictinfo.c4
-rw-r--r--src/backend/optimizer/util/tlist.c46
-rw-r--r--src/backend/optimizer/util/var.c7
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), &ltproc);
- greaterstr = make_greater_string(prefix_const, &ltproc);
+ greaterstr = make_greater_string(prefix_const, &ltproc, 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);