1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* paths.h
|
2005-06-06 00:32:58 +02:00
|
|
|
* prototypes for various files in optimizer/path
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
2020-01-01 18:21:45 +01:00
|
|
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/include/optimizer/paths.h
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
#ifndef PATHS_H
|
|
|
|
#define PATHS_H
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2019-01-29 22:49:25 +01:00
|
|
|
#include "nodes/pathnodes.h"
|
1997-11-26 02:14:33 +01:00
|
|
|
|
2003-01-24 04:58:44 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1999-07-27 08:23:12 +02:00
|
|
|
* allpaths.c
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
2018-02-09 21:54:45 +01:00
|
|
|
extern PGDLLIMPORT bool enable_geqo;
|
2018-04-26 20:47:16 +02:00
|
|
|
extern PGDLLIMPORT int geqo_threshold;
|
|
|
|
extern PGDLLIMPORT int min_parallel_table_scan_size;
|
|
|
|
extern PGDLLIMPORT int min_parallel_index_scan_size;
|
2014-11-21 20:05:46 +01:00
|
|
|
|
|
|
|
/* Hook for plugins to get control in set_rel_pathlist() */
|
|
|
|
typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root,
|
2017-06-21 20:39:04 +02:00
|
|
|
RelOptInfo *rel,
|
|
|
|
Index rti,
|
|
|
|
RangeTblEntry *rte);
|
2014-11-21 20:05:46 +01:00
|
|
|
extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook;
|
2015-05-01 14:50:35 +02:00
|
|
|
|
|
|
|
/* Hook for plugins to get control in add_paths_to_joinrel() */
|
|
|
|
typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root,
|
2017-06-21 20:39:04 +02:00
|
|
|
RelOptInfo *joinrel,
|
|
|
|
RelOptInfo *outerrel,
|
|
|
|
RelOptInfo *innerrel,
|
|
|
|
JoinType jointype,
|
|
|
|
JoinPathExtraData *extra);
|
2015-05-01 14:50:35 +02:00
|
|
|
extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook;
|
2000-01-23 00:50:30 +01:00
|
|
|
|
2007-09-26 20:51:51 +02:00
|
|
|
/* Hook for plugins to replace standard_join_search() */
|
2007-11-15 22:14:46 +01:00
|
|
|
typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root,
|
2017-06-21 20:39:04 +02:00
|
|
|
int levels_needed,
|
|
|
|
List *initial_rels);
|
2007-09-26 20:51:51 +02:00
|
|
|
extern PGDLLIMPORT join_search_hook_type join_search_hook;
|
|
|
|
|
|
|
|
|
2005-12-20 03:30:36 +01:00
|
|
|
extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
|
2007-09-26 20:51:51 +02:00
|
|
|
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *initial_rels);
|
1996-07-09 08:22:35 +02:00
|
|
|
|
Add a new upper planner relation for partially-aggregated results.
Up until now, we've abused grouped_rel->partial_pathlist as a place to
store partial paths that have been partially aggregate, but that's
really not correct, because a partial path for a relation is supposed
to be one which produces the correct results with the addition of only
a Gather or Gather Merge node, and these paths also require a Finalize
Aggregate step. Instead, add a new partially_group_rel which can hold
either partial paths (which need to be gathered and then have
aggregation finalized) or non-partial paths (which only need to have
aggregation finalized). This allows us to reuse generate_gather_paths
for partially_grouped_rel instead of writing new code, so that this
patch actually basically no net new code while making things cleaner,
simplifying things for pending patches for partition-wise aggregate.
Robert Haas and Jeevan Chalke. The larger patch series of which this
patch is a part was also reviewed and tested by Antonin Houska,
Rajkumar Raghuwanshi, David Rowley, Dilip Kumar, Konstantin Knizhnik,
Pascal Legrand, Rafia Sabih, and me.
Discussion: http://postgr.es/m/CA+TgmobrzFYS3+U8a_BCy3-hOvh5UyJbC18rEcYehxhpw5=ETA@mail.gmail.com
Discussion: http://postgr.es/m/CA+TgmoZyQEjdBNuoG9-wC5GQ5GrO4544Myo13dVptvx+uLg9uQ@mail.gmail.com
2018-02-26 15:30:12 +01:00
|
|
|
extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
|
2019-05-22 19:04:48 +02:00
|
|
|
bool override_rows);
|
2020-04-07 16:43:18 +02:00
|
|
|
extern void generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel,
|
|
|
|
bool override_rows);
|
2019-05-22 19:04:48 +02:00
|
|
|
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
|
|
|
|
double index_pages, int max_workers);
|
Support parallel bitmap heap scans.
The index is scanned by a single process, but then all cooperating
processes can iterate jointly over the resulting set of heap blocks.
In the future, we might also want to support using a parallel bitmap
index scan to set up for a parallel bitmap heap scan, but that's a
job for another day.
Dilip Kumar, with some corrections and cosmetic changes by me. The
larger patch set of which this is a part has been reviewed and tested
by (at least) Andres Freund, Amit Khandekar, Tushar Ahuja, Rafia
Sabih, Haribabu Kommi, Thomas Munro, and me.
Discussion: http://postgr.es/m/CAFiTN-uc4=0WxRGfCzs-xfkMYcSEWUC-Fon6thkJGjkh9i=13A@mail.gmail.com
2017-03-08 18:05:43 +01:00
|
|
|
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
|
2019-05-22 19:04:48 +02:00
|
|
|
Path *bitmapqual);
|
2018-02-16 16:33:59 +01:00
|
|
|
extern void generate_partitionwise_join_paths(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel);
|
2016-01-20 20:29:22 +01:00
|
|
|
|
2001-10-18 18:11:42 +02:00
|
|
|
#ifdef OPTIMIZER_DEBUG
|
2005-06-06 00:32:58 +02:00
|
|
|
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
|
2001-10-18 18:11:42 +02:00
|
|
|
#endif
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1999-07-27 08:23:12 +02:00
|
|
|
* indxpath.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* routines to generate index paths
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
|
2009-09-17 22:49:29 +02:00
|
|
|
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *restrictlist,
|
|
|
|
List *exprlist, List *oprlist);
|
2017-01-15 20:09:35 +01:00
|
|
|
extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
|
2019-05-22 19:04:48 +02:00
|
|
|
int indexcol);
|
2005-04-12 01:06:57 +02:00
|
|
|
extern bool match_index_to_operand(Node *operand, int indexcol,
|
2019-05-22 19:04:48 +02:00
|
|
|
IndexOptInfo *index);
|
Support using index-only scans with partial indexes in more cases.
Previously, the planner would reject an index-only scan if any restriction
clause for its table used a column not available from the index, even
if that restriction clause would later be dropped from the plan entirely
because it's implied by the index's predicate. This is a fairly common
situation for partial indexes because predicates using columns not included
in the index are often the most useful kind of predicate, and we have to
duplicate (or at least imply) the predicate in the WHERE clause in order
to get the index to be considered at all. So index-only scans were
essentially unavailable with such partial indexes.
To fix, we have to do detection of implied-by-predicate clauses much
earlier in the planner. This patch puts it in check_index_predicates
(nee check_partial_indexes), meaning it gets done for every partial index,
whereas we previously only considered this issue at createplan time,
so that the work was only done for an index actually selected for use.
That could result in a noticeable planning slowdown for queries against
tables with many partial indexes. However, testing suggested that there
isn't really a significant cost, especially not with reasonable numbers
of partial indexes. We do get a small additional benefit, which is that
cost_index is more accurate since it correctly discounts the evaluation
cost of clauses that will be removed. We can also avoid considering such
clauses as potential indexquals, which saves useless matching cycles in
the case where the predicate columns aren't in the index, and prevents
generating bogus plans that double-count the clause's selectivity when
the columns are in the index.
Tomas Vondra and Kyotaro Horiguchi, reviewed by Kevin Grittner and
Konstantin Knizhnik, and whacked around a little by me
2016-03-31 20:48:56 +02:00
|
|
|
extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1999-11-23 21:07:06 +01:00
|
|
|
/*
|
|
|
|
* tidpath.h
|
|
|
|
* routines to generate tid paths
|
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
extern void create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel);
|
1999-11-23 21:07:06 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1999-07-27 08:23:12 +02:00
|
|
|
* joinpath.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* routines to create join paths
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *outerrel, RelOptInfo *innerrel,
|
|
|
|
JoinType jointype, SpecialJoinInfo *sjinfo,
|
|
|
|
List *restrictlist);
|
2000-02-07 05:41:04 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* joinrels.c
|
|
|
|
* routines to determine which relations to join
|
|
|
|
*/
|
2009-11-28 01:46:19 +01:00
|
|
|
extern void join_search_one_level(PlannerInfo *root, int level);
|
2005-06-06 00:32:58 +02:00
|
|
|
extern RelOptInfo *make_join_rel(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel1, RelOptInfo *rel2);
|
Restructure code that is responsible for ensuring that clauseless joins are
considered when it is necessary to do so because of a join-order restriction
(that is, an outer-join or IN-subselect construct). The former coding was a
bit ad-hoc and inconsistent, and it missed some cases, as exposed by Mario
Weilguni's recent bug report. His specific problem was that an IN could be
turned into a "clauseless" join due to constant-propagation removing the IN's
joinclause, and if the IN's subselect involved more than one relation and
there was more than one such IN linking to the same upper relation, then the
only valid join orders involve "bushy" plans but we would fail to consider the
specific paths needed to get there. (See the example case added to the join
regression test.) On examining the code I wonder if there weren't some other
problem cases too; in particular it seems that GEQO was defending against a
different set of corner cases than the main planner was. There was also an
efficiency problem, in that when we did realize we needed a clauseless join
because of an IN, we'd consider clauseless joins against every other relation
whether this was sensible or not. It seems a better design is to use the
outer-join and in-clause lists as a backup heuristic, just as the rule of
joining only where there are joinclauses is a heuristic: we'll join two
relations if they have a usable joinclause *or* this might be necessary to
satisfy an outer-join or IN-clause join order restriction. I refactored the
code to have just one place considering this instead of three, and made sure
that it covered all the cases that any of them had been considering.
Backpatch as far as 8.1 (which has only the IN-clause form of the disease).
By rights 8.0 and 7.4 should have the bug too, but they accidentally fail
to fail, because the joininfo structure used in those releases preserves some
memory of there having once been a joinclause between the inner and outer
sides of an IN, and so it leads the code in the right direction anyway.
I'll be conservative and not touch them.
2007-02-16 01:14:01 +01:00
|
|
|
extern bool have_join_order_restriction(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel1, RelOptInfo *rel2);
|
Still more fixes for planner's handling of LATERAL references.
More fuzz testing by Andreas Seltenreich exposed that the planner did not
cope well with chains of lateral references. If relation X references Y
laterally, and Y references Z laterally, then we will have to scan X on the
inside of a nestloop with Z, so for all intents and purposes X is laterally
dependent on Z too. The planner did not understand this and would generate
intermediate joins that could not be used. While that was usually harmless
except for wasting some planning cycles, under the right circumstances it
would lead to "failed to build any N-way joins" or "could not devise a
query plan" planner failures.
To fix that, convert the existing per-relation lateral_relids and
lateral_referencers relid sets into their transitive closures; that is,
they now show all relations on which a rel is directly or indirectly
laterally dependent. This not only fixes the chained-reference problem
but allows some of the relevant tests to be made substantially simpler
and faster, since they can be reduced to simple bitmap manipulations
instead of searches of the LateralJoinInfo list.
Also, when a PlaceHolderVar that is due to be evaluated at a join contains
lateral references, we should treat those references as indirect lateral
dependencies of each of the join's base relations. This prevents us from
trying to join any individual base relations to the lateral reference
source before the join is formed, which again cannot work.
Andreas' testing also exposed another oversight in the "dangerous
PlaceHolderVar" test added in commit 85e5e222b1dd02f1. Simply rejecting
unsafe join paths in joinpath.c is insufficient, because in some cases
we will end up rejecting *all* possible paths for a particular join, again
leading to "could not devise a query plan" failures. The restriction has
to be known also to join_is_legal and its cohort functions, so that they
will not select a join for which that will happen. I chose to move the
supporting logic into joinrels.c where the latter functions are.
Back-patch to 9.3 where LATERAL support was introduced.
2015-12-11 20:22:20 +01:00
|
|
|
extern bool have_dangerous_phv(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
Relids outer_relids, Relids inner_params);
|
Basic partition-wise join functionality.
Instead of joining two partitioned tables in their entirety we can, if
it is an equi-join on the partition keys, join the matching partitions
individually. This involves teaching the planner about "other join"
rels, which are related to regular join rels in the same way that
other member rels are related to baserels. This can use significantly
more CPU time and memory than regular join planning, because there may
now be a set of "other" rels not only for every base relation but also
for every join relation. In most practical cases, this probably
shouldn't be a problem, because (1) it's probably unusual to join many
tables each with many partitions using the partition keys for all
joins and (2) if you do that scenario then you probably have a big
enough machine to handle the increased memory cost of planning and (3)
the resulting plan is highly likely to be better, so what you spend in
planning you'll make up on the execution side. All the same, for now,
turn this feature off by default.
Currently, we can only perform joins between two tables whose
partitioning schemes are absolutely identical. It would be nice to
cope with other scenarios, such as extra partitions on one side or the
other with no match on the other side, but that will have to wait for
a future patch.
Ashutosh Bapat, reviewed and tested by Rajkumar Raghuwanshi, Amit
Langote, Rafia Sabih, Thomas Munro, Dilip Kumar, Antonin Houska, Amit
Khandekar, and by me. A few final adjustments by me.
Discussion: http://postgr.es/m/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj=EaDTSA@mail.gmail.com
Discussion: http://postgr.es/m/CAFjFpRcitjfrULr5jfuKWRPsGUX0LQ0k8-yG0Qw2+1LBGNpMdw@mail.gmail.com
2017-10-06 17:11:10 +02:00
|
|
|
extern void mark_dummy_rel(RelOptInfo *rel);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2007-01-20 21:45:41 +01:00
|
|
|
/*
|
|
|
|
* equivclass.c
|
|
|
|
* routines for managing EquivalenceClasses
|
|
|
|
*/
|
2013-03-22 00:43:59 +01:00
|
|
|
typedef bool (*ec_matches_callback_type) (PlannerInfo *root,
|
2017-06-21 20:39:04 +02:00
|
|
|
RelOptInfo *rel,
|
|
|
|
EquivalenceClass *ec,
|
|
|
|
EquivalenceMember *em,
|
|
|
|
void *arg);
|
2013-03-22 00:43:59 +01:00
|
|
|
|
Reduce "X = X" to "X IS NOT NULL", if it's easy to do so.
If the operator is a strict btree equality operator, and X isn't volatile,
then the clause must yield true for any non-null value of X, or null if X
is null. At top level of a WHERE clause, we can ignore the distinction
between false and null results, so it's valid to simplify the clause to
"X IS NOT NULL". This is a useful improvement mainly because we'll get
a far better selectivity estimate in most cases.
Because such cases seldom arise in well-written queries, it is unappetizing
to expend a lot of planner cycles looking for them ... but it turns out
that there's a place we can shoehorn this in practically for free, because
equivclass.c already has to detect and reject candidate equivalences of the
form X = X. That doesn't catch every place that it would be valid to
simplify to X IS NOT NULL, but it catches the typical case. Working harder
doesn't seem justified.
Patch by me, reviewed by Petr Jelinek
Discussion: https://postgr.es/m/CAMjNa7cC4X9YR-vAJS-jSYCajhRDvJQnN7m2sLH1wLh-_Z2bsw@mail.gmail.com
2017-10-08 18:23:32 +02:00
|
|
|
extern bool process_equivalence(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RestrictInfo **p_restrictinfo,
|
|
|
|
bool below_outer_join);
|
2011-03-20 01:29:08 +01:00
|
|
|
extern Expr *canonicalize_ec_expression(Expr *expr,
|
2019-05-22 19:04:48 +02:00
|
|
|
Oid req_type, Oid req_collation);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern void reconsider_outer_join_clauses(PlannerInfo *root);
|
|
|
|
extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
Expr *expr,
|
|
|
|
Relids nullable_relids,
|
|
|
|
List *opfamilies,
|
|
|
|
Oid opcintype,
|
|
|
|
Oid collation,
|
|
|
|
Index sortref,
|
|
|
|
Relids rel,
|
|
|
|
bool create_it);
|
2020-04-07 16:43:18 +02:00
|
|
|
extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
|
2020-12-21 18:29:46 +01:00
|
|
|
extern Expr *find_em_expr_usable_for_sorting_rel(PlannerInfo *root,
|
|
|
|
EquivalenceClass *ec,
|
|
|
|
RelOptInfo *rel,
|
|
|
|
bool require_parallel_safe);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern void generate_base_implied_equalities(PlannerInfo *root);
|
|
|
|
extern List *generate_join_implied_equalities(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
Relids join_relids,
|
|
|
|
Relids outer_relids,
|
|
|
|
RelOptInfo *inner_rel);
|
Fix mishandling of equivalence-class tests in parameterized plans.
Given a three-or-more-way equivalence class, such as X.Y = Y.Y = Z.Z,
it was possible for the planner to omit one of the quals needed to
enforce that all members of the equivalence class are actually equal.
This only happened in the case of a parameterized join node for two
of the relations, that is a plan tree like
Nested Loop
-> Scan X
-> Nested Loop
-> Scan Y
-> Scan Z
Filter: Z.Z = X.X
The eclass machinery normally expects to apply X.X = Y.Y when those
two relations are joined, but in this shape of plan tree they aren't
joined until the top node --- and, if the lower nested loop is marked
as parameterized by X, the top node will assume that the relevant eclass
condition(s) got pushed down into the lower node. On the other hand,
the scan of Z assumes that it's only responsible for constraining Z.Z
to match any one of the other eclass members. So one or another of
the required quals sometimes fell between the cracks, depending on
whether consideration of the eclass in get_joinrel_parampathinfo()
for the lower nested loop chanced to generate X.X = Y.Y or X.X = Z.Z
as the appropriate constraint there. If it generated the latter,
it'd erroneously suppose that the Z scan would take care of matters.
To fix, force X.X = Y.Y to be generated and applied at that join node
when this case occurs.
This is *extremely* hard to hit in practice, because various planner
behaviors conspire to mask the problem; starting with the fact that the
planner doesn't really like to generate a parameterized plan of the
above shape. (It might have been impossible to hit it before we
tweaked things to allow this plan shape for star-schema cases.) Many
thanks to Alexander Kirkouski for submitting a reproducible test case.
The bug can be demonstrated in all branches back to 9.2 where parameterized
paths were introduced, so back-patch that far.
2016-04-30 02:19:38 +02:00
|
|
|
extern List *generate_join_implied_equalities_for_ecs(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *eclasses,
|
|
|
|
Relids join_relids,
|
|
|
|
Relids outer_relids,
|
|
|
|
RelOptInfo *inner_rel);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
|
2016-06-18 21:22:34 +02:00
|
|
|
extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
ForeignKeyOptInfo *fkinfo,
|
|
|
|
int colno);
|
Fix foreign-key selectivity estimation in the presence of constants.
get_foreign_key_join_selectivity() looks for join clauses that equate
the two sides of the FK constraint. However, if we have a query like
"WHERE fktab.a = pktab.a and fktab.a = 1", it won't find any such join
clause, because equivclass.c replaces the given clauses with "fktab.a
= 1 and pktab.a = 1", which can be enforced at the scan level, leaving
nothing to be done for column "a" at the join level.
We can fix that expectation without much trouble, but then a new problem
arises: applying the foreign-key-based selectivity rule produces a
rowcount underestimate, because we're effectively double-counting the
selectivity of the "fktab.a = 1" clause. So we have to cancel that
selectivity out of the estimate.
To fix, refactor process_implied_equality() so that it can pass back the
new RestrictInfo to its callers in equivclass.c, allowing the generated
"fktab.a = 1" clause to be saved in the EquivalenceClass's ec_derives
list. Then it's not much trouble to dig out the relevant RestrictInfo
when we need to adjust an FK selectivity estimate. (While at it, we
can also remove the expensive use of initialize_mergeclause_eclasses()
to set up the new RestrictInfo's left_ec and right_ec pointers.
The equivclass.c code can set those basically for free.)
This seems like clearly a bug fix, but I'm hesitant to back-patch it,
first because there's some API/ABI risk for extensions and second because
we're usually loath to destabilize plan choices in stable branches.
Per report from Sigrid Ehrenreich.
Discussion: https://postgr.es/m/1019549.1603770457@sss.pgh.pa.us
Discussion: https://postgr.es/m/AM6PR02MB5287A0ADD936C1FA80973E72AB190@AM6PR02MB5287.eurprd02.prod.outlook.com
2020-10-28 16:15:47 +01:00
|
|
|
extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
|
|
|
|
EquivalenceMember *em);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern void add_child_rel_equivalences(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
AppendRelInfo *appinfo,
|
|
|
|
RelOptInfo *parent_rel,
|
|
|
|
RelOptInfo *child_rel);
|
2019-11-05 17:42:24 +01:00
|
|
|
extern void add_child_join_rel_equivalences(PlannerInfo *root,
|
|
|
|
int nappinfos,
|
|
|
|
AppendRelInfo **appinfos,
|
|
|
|
RelOptInfo *parent_rel,
|
|
|
|
RelOptInfo *child_rel);
|
2013-03-22 00:43:59 +01:00
|
|
|
extern List *generate_implied_equalities_for_column(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel,
|
|
|
|
ec_matches_callback_type callback,
|
|
|
|
void *callback_arg,
|
|
|
|
Relids prohibited_rels);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern bool have_relevant_eclass_joinclause(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel1, RelOptInfo *rel2);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel1);
|
Fix eclass_useful_for_merging to give valid results for appendrel children.
Formerly, this function would always return "true" for an appendrel child
relation, because it would think that the appendrel parent was a potential
join target for the child. In principle that should only lead to some
inefficiency in planning, but fuzz testing by Andreas Seltenreich disclosed
that it could lead to "could not find pathkey item to sort" planner errors
in odd corner cases. Specifically, we would think that all columns of a
child table's multicolumn index were interesting pathkeys, causing us to
generate a MergeAppend path that sorts by all the columns. However, if any
of those columns weren't actually used above the level of the appendrel,
they would not get added to that rel's targetlist, which would result in
being unable to resolve the MergeAppend's sort keys against its targetlist
during createplan.c.
Backpatch to 9.3. In older versions, columns of an appendrel get added
to its targetlist even if they're not mentioned above the scan level,
so that the failure doesn't occur. It might be worth back-patching this
fix to older versions anyway, but I'll refrain for the moment.
2015-08-07 02:14:37 +02:00
|
|
|
extern bool eclass_useful_for_merging(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
EquivalenceClass *eclass,
|
|
|
|
RelOptInfo *rel);
|
Revise parameterized-path mechanism to fix assorted issues.
This patch adjusts the treatment of parameterized paths so that all paths
with the same parameterization (same set of required outer rels) for the
same relation will have the same rowcount estimate. We cache the rowcount
estimates to ensure that property, and hopefully save a few cycles too.
Doing this makes it practical for add_path_precheck to operate without
a rowcount estimate: it need only assume that paths with different
parameterizations never dominate each other, which is close enough to
true anyway for coarse filtering, because normally a more-parameterized
path should yield fewer rows thanks to having more join clauses to apply.
In add_path, we do the full nine yards of comparing rowcount estimates
along with everything else, so that we can discard parameterized paths that
don't actually have an advantage. This fixes some issues I'd found with
add_path rejecting parameterized paths on the grounds that they were more
expensive than not-parameterized ones, even though they yielded many fewer
rows and hence would be cheaper once subsequent joining was considered.
To make the same-rowcounts assumption valid, we have to require that any
parameterized path enforce *all* join clauses that could be obtained from
the particular set of outer rels, even if not all of them are useful for
indexing. This is required at both base scans and joins. It's a good
thing anyway since the net impact is that join quals are checked at the
lowest practical level in the join tree. Hence, discard the original
rather ad-hoc mechanism for choosing parameterization joinquals, and build
a better one that has a more principled rule for when clauses can be moved.
The original rule was actually buggy anyway for lack of knowledge about
which relations are part of an outer join's outer side; getting this right
requires adding an outer_relids field to RestrictInfo.
2012-04-19 21:52:46 +02:00
|
|
|
extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
|
Refactor the representation of indexable clauses in IndexPaths.
In place of three separate but interrelated lists (indexclauses,
indexquals, and indexqualcols), an IndexPath now has one list
"indexclauses" of IndexClause nodes. This holds basically the same
information as before, but in a more useful format: in particular, there
is now a clear connection between an indexclause (an original restriction
clause from WHERE or JOIN/ON) and the indexquals (directly usable index
conditions) derived from it.
We also change the ground rules a bit by mandating that clause commutation,
if needed, be done up-front so that what is stored in the indexquals list
is always directly usable as an index condition. This gets rid of repeated
re-determination of which side of the clause is the indexkey during costing
and plan generation, as well as repeated lookups of the commutator
operator. To minimize the added up-front cost, the typical case of
commuting a plain OpExpr is handled by a new special-purpose function
commute_restrictinfo(). For RowCompareExprs, generating the new clause
properly commuted to begin with is not really any more complex than before,
it's just different --- and we can save doing that work twice, as the
pretty-klugy original implementation did.
Tracking the connection between original and derived clauses lets us
also track explicitly whether the derived clauses are an exact or lossy
translation of the original. This provides a cheap solution to getting
rid of unnecessary rechecks of boolean index clauses, which previously
seemed like it'd be more expensive than it was worth.
Another pleasant (IMO) side-effect is that EXPLAIN now always shows
index clauses with the indexkey on the left; this seems less confusing.
This commit leaves expand_indexqual_conditions() and some related
functions in a slightly messy state. I didn't bother to change them
any more than minimally necessary to work with the new data structure,
because all that code is going to be refactored out of existence in
a follow-on patch.
Discussion: https://postgr.es/m/22182.1549124950@sss.pgh.pa.us
2019-02-09 23:30:43 +01:00
|
|
|
extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *indexclauses);
|
2007-01-20 21:45:41 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1999-08-16 04:17:58 +02:00
|
|
|
* pathkeys.c
|
|
|
|
* utilities for matching and building path keys
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1999-08-16 04:17:58 +02:00
|
|
|
typedef enum
|
|
|
|
{
|
2001-10-28 07:26:15 +01:00
|
|
|
PATHKEYS_EQUAL, /* pathkeys are identical */
|
|
|
|
PATHKEYS_BETTER1, /* pathkey 1 is a superset of pathkey 2 */
|
|
|
|
PATHKEYS_BETTER2, /* vice versa */
|
|
|
|
PATHKEYS_DIFFERENT /* neither pathkey includes the other */
|
1999-08-16 04:17:58 +02:00
|
|
|
} PathKeysComparison;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1999-08-16 04:17:58 +02:00
|
|
|
extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2);
|
|
|
|
extern bool pathkeys_contained_in(List *keys1, List *keys2);
|
Implement Incremental Sort
Incremental Sort is an optimized variant of multikey sort for cases when
the input is already sorted by a prefix of the requested sort keys. For
example when the relation is already sorted by (key1, key2) and we need
to sort it by (key1, key2, key3) we can simply split the input rows into
groups having equal values in (key1, key2), and only sort/compare the
remaining column key3.
This has a number of benefits:
- Reduced memory consumption, because only a single group (determined by
values in the sorted prefix) needs to be kept in memory. This may also
eliminate the need to spill to disk.
- Lower startup cost, because Incremental Sort produce results after each
prefix group, which is beneficial for plans where startup cost matters
(like for example queries with LIMIT clause).
We consider both Sort and Incremental Sort, and decide based on costing.
The implemented algorithm operates in two different modes:
- Fetching a minimum number of tuples without check of equality on the
prefix keys, and sorting on all columns when safe.
- Fetching all tuples for a single prefix group and then sorting by
comparing only the remaining (non-prefix) keys.
We always start in the first mode, and employ a heuristic to switch into
the second mode if we believe it's beneficial - the goal is to minimize
the number of unnecessary comparions while keeping memory consumption
below work_mem.
This is a very old patch series. The idea was originally proposed by
Alexander Korotkov back in 2013, and then revived in 2017. In 2018 the
patch was taken over by James Coleman, who wrote and rewrote most of the
current code.
There were many reviewers/contributors since 2013 - I've done my best to
pick the most active ones, and listed them in this commit message.
Author: James Coleman, Alexander Korotkov
Reviewed-by: Tomas Vondra, Andreas Karlsson, Marti Raudsepp, Peter Geoghegan, Robert Haas, Thomas Munro, Antonin Houska, Andres Freund, Alexander Kuzmenkov
Discussion: https://postgr.es/m/CAPpHfdscOX5an71nHd8WSUH6GNOCf=V7wgDaTXdDd9=goN-gfA@mail.gmail.com
Discussion: https://postgr.es/m/CAPpHfds1waRZ=NOmueYq0sx1ZSCnt+5QJvizT8ndT2=etZEeAQ@mail.gmail.com
2020-04-06 21:33:28 +02:00
|
|
|
extern bool pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common);
|
1999-08-21 05:49:17 +02:00
|
|
|
extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys,
|
2019-05-22 19:04:48 +02:00
|
|
|
Relids required_outer,
|
|
|
|
CostSelector cost_criterion,
|
|
|
|
bool require_parallel_safe);
|
2000-02-15 21:49:31 +01:00
|
|
|
extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *pathkeys,
|
|
|
|
Relids required_outer,
|
|
|
|
double fraction);
|
2017-03-07 16:33:29 +01:00
|
|
|
extern Path *get_cheapest_parallel_safe_total_inner(List *paths);
|
2005-06-06 00:32:58 +02:00
|
|
|
extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index,
|
2019-05-22 19:04:48 +02:00
|
|
|
ScanDirection scandir);
|
Use Append rather than MergeAppend for scanning ordered partitions.
If we need ordered output from a scan of a partitioned table, but
the ordering matches the partition ordering, then we don't need to
use a MergeAppend to combine the pre-ordered per-partition scan
results: a plain Append will produce the same results. This
both saves useless comparison work inside the MergeAppend proper,
and allows us to start returning tuples after istarting up just
the first child node not all of them.
However, all is not peaches and cream, because if some of the
child nodes have high startup costs then there will be big
discontinuities in the tuples-returned-versus-elapsed-time curve.
The planner's cost model cannot handle that (yet, anyway).
If we model the Append's startup cost as being just the first
child's startup cost, we may drastically underestimate the cost
of fetching slightly more tuples than are available from the first
child. Since we've had bad experiences with over-optimistic choices
of "fast start" plans for ORDER BY LIMIT queries, that seems scary.
As a klugy workaround, set the startup cost estimate for an ordered
Append to be the sum of its children's startup costs (as MergeAppend
would). This doesn't really describe reality, but it's less likely
to cause a bad plan choice than an underestimated startup cost would.
In practice, the cases where we really care about this optimization
will have child plans that are IndexScans with zero startup cost,
so that the overly conservative estimate is still just zero.
David Rowley, reviewed by Julien Rouhaud and Antonin Houska
Discussion: https://postgr.es/m/CAKJS1f-hAqhPLRk_RaSFTgYxd=Tz5hA7kQ2h4-DhJufQk8TGuw@mail.gmail.com
2019-04-06 01:20:30 +02:00
|
|
|
extern List *build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel,
|
2019-05-22 19:04:48 +02:00
|
|
|
ScanDirection scandir, bool *partialkeys);
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr,
|
2019-05-22 19:04:48 +02:00
|
|
|
Relids nullable_relids, Oid opno,
|
|
|
|
Relids rel, bool create_it);
|
2005-06-06 00:32:58 +02:00
|
|
|
extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *subquery_pathkeys,
|
|
|
|
List *subquery_tlist);
|
2005-06-06 00:32:58 +02:00
|
|
|
extern List *build_join_pathkeys(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *joinrel,
|
|
|
|
JoinType jointype,
|
|
|
|
List *outer_pathkeys);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *sortclauses,
|
|
|
|
List *tlist);
|
2010-10-29 17:52:16 +02:00
|
|
|
extern void initialize_mergeclause_eclasses(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RestrictInfo *restrictinfo);
|
2010-10-29 17:52:16 +02:00
|
|
|
extern void update_mergeclause_eclasses(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RestrictInfo *restrictinfo);
|
Fix planner failures with overlapping mergejoin clauses in an outer join.
Given overlapping or partially redundant join clauses, for example
t1 JOIN t2 ON t1.a = t2.x AND t1.b = t2.x
the planner's EquivalenceClass machinery will ordinarily refactor the
clauses as "t1.a = t1.b AND t1.a = t2.x", so that join processing doesn't
see multiple references to the same EquivalenceClass in a list of join
equality clauses. However, if the join is outer, it's incorrect to derive
a restriction clause on the outer side from the join conditions, so the
clause refactoring does not happen and we end up with overlapping join
conditions. The code that attempted to deal with such cases had several
subtle bugs, which could result in "left and right pathkeys do not match in
mergejoin" or "outer pathkeys do not match mergeclauses" planner errors,
if the selected join plan type was a mergejoin. (It does not appear that
any actually incorrect plan could have been emitted.)
The core of the problem really was failure to recognize that the outer and
inner relations' pathkeys have different relationships to the mergeclause
list. A join's mergeclause list is constructed by reference to the outer
pathkeys, so it will always be ordered the same as the outer pathkeys, but
this cannot be presumed true for the inner pathkeys. If the inner sides of
the mergeclauses contain multiple references to the same EquivalenceClass
({t2.x} in the above example) then a simplistic rendering of the required
inner sort order is like "ORDER BY t2.x, t2.x", but the pathkey machinery
recognizes that the second sort column is redundant and throws it away.
The mergejoin planning code failed to account for that behavior properly.
One error was to try to generate cut-down versions of the mergeclause list
from cut-down versions of the inner pathkeys in the same way as the initial
construction of the mergeclause list from the outer pathkeys was done; this
could lead to choosing a mergeclause list that fails to match the outer
pathkeys. The other problem was that the pathkey cross-checking code in
create_mergejoin_plan treated the inner and outer pathkey lists
identically, whereas actually the expectations for them must be different.
That led to false "pathkeys do not match" failures in some cases, and in
principle could have led to failure to detect bogus plans in other cases,
though there is no indication that such bogus plans could be generated.
Reported by Alexander Kuzmenkov, who also reviewed this patch. This has
been broken for years (back to around 8.3 according to my testing), so
back-patch to all supported branches.
Discussion: https://postgr.es/m/5dad9160-4632-0e47-e120-8e2082000c01@postgrespro.ru
2018-02-23 19:47:33 +01:00
|
|
|
extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *pathkeys,
|
|
|
|
List *restrictinfos);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *mergeclauses,
|
|
|
|
RelOptInfo *joinrel);
|
2007-01-20 21:45:41 +01:00
|
|
|
extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *mergeclauses,
|
|
|
|
List *outer_pathkeys);
|
Fix planner failures with overlapping mergejoin clauses in an outer join.
Given overlapping or partially redundant join clauses, for example
t1 JOIN t2 ON t1.a = t2.x AND t1.b = t2.x
the planner's EquivalenceClass machinery will ordinarily refactor the
clauses as "t1.a = t1.b AND t1.a = t2.x", so that join processing doesn't
see multiple references to the same EquivalenceClass in a list of join
equality clauses. However, if the join is outer, it's incorrect to derive
a restriction clause on the outer side from the join conditions, so the
clause refactoring does not happen and we end up with overlapping join
conditions. The code that attempted to deal with such cases had several
subtle bugs, which could result in "left and right pathkeys do not match in
mergejoin" or "outer pathkeys do not match mergeclauses" planner errors,
if the selected join plan type was a mergejoin. (It does not appear that
any actually incorrect plan could have been emitted.)
The core of the problem really was failure to recognize that the outer and
inner relations' pathkeys have different relationships to the mergeclause
list. A join's mergeclause list is constructed by reference to the outer
pathkeys, so it will always be ordered the same as the outer pathkeys, but
this cannot be presumed true for the inner pathkeys. If the inner sides of
the mergeclauses contain multiple references to the same EquivalenceClass
({t2.x} in the above example) then a simplistic rendering of the required
inner sort order is like "ORDER BY t2.x, t2.x", but the pathkey machinery
recognizes that the second sort column is redundant and throws it away.
The mergejoin planning code failed to account for that behavior properly.
One error was to try to generate cut-down versions of the mergeclause list
from cut-down versions of the inner pathkeys in the same way as the initial
construction of the mergeclause list from the outer pathkeys was done; this
could lead to choosing a mergeclause list that fails to match the outer
pathkeys. The other problem was that the pathkey cross-checking code in
create_mergejoin_plan treated the inner and outer pathkey lists
identically, whereas actually the expectations for them must be different.
That led to false "pathkeys do not match" failures in some cases, and in
principle could have led to failure to detect bogus plans in other cases,
though there is no indication that such bogus plans could be generated.
Reported by Alexander Kuzmenkov, who also reviewed this patch. This has
been broken for years (back to around 8.3 according to my testing), so
back-patch to all supported branches.
Discussion: https://postgr.es/m/5dad9160-4632-0e47-e120-8e2082000c01@postgrespro.ru
2018-02-23 19:47:33 +01:00
|
|
|
extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *mergeclauses,
|
|
|
|
List *pathkeys);
|
2005-06-06 00:32:58 +02:00
|
|
|
extern List *truncate_useless_pathkeys(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
RelOptInfo *rel,
|
|
|
|
List *pathkeys);
|
2007-04-15 22:09:28 +02:00
|
|
|
extern bool has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel);
|
2015-12-22 19:46:40 +01:00
|
|
|
extern PathKey *make_canonical_pathkey(PlannerInfo *root,
|
2019-05-22 19:04:48 +02:00
|
|
|
EquivalenceClass *eclass, Oid opfamily,
|
|
|
|
int strategy, bool nulls_first);
|
Implement partition-wise grouping/aggregation.
If the partition keys of input relation are part of the GROUP BY
clause, all the rows belonging to a given group come from a single
partition. This allows aggregation/grouping over a partitioned
relation to be broken down * into aggregation/grouping on each
partition. This should be no worse, and often better, than the normal
approach.
If the GROUP BY clause does not contain all the partition keys, we can
still perform partial aggregation for each partition and then finalize
aggregation after appending the partial results. This is less certain
to be a win, but it's still useful.
Jeevan Chalke, Ashutosh Bapat, Robert Haas. The larger patch series
of which this patch is a part was also reviewed and tested by Antonin
Houska, Rajkumar Raghuwanshi, David Rowley, Dilip Kumar, Konstantin
Knizhnik, Pascal Legrand, and Rafia Sabih.
Discussion: http://postgr.es/m/CAM2+6=V64_xhstVHie0Rz=KPEQnLJMZt_e314P0jaT_oJ9MR8A@mail.gmail.com
2018-03-22 17:49:48 +01:00
|
|
|
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
|
2019-05-22 19:04:48 +02:00
|
|
|
List *live_childrels);
|
2001-10-28 07:26:15 +01:00
|
|
|
|
Phase 2 of pgindent updates.
Change pg_bsd_indent to follow upstream rules for placement of comments
to the right of code, and remove pgindent hack that caused comments
following #endif to not obey the general rule.
Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using
the published version of pg_bsd_indent, but a hacked-up version that
tried to minimize the amount of movement of comments to the right of
code. The situation of interest is where such a comment has to be
moved to the right of its default placement at column 33 because there's
code there. BSD indent has always moved right in units of tab stops
in such cases --- but in the previous incarnation, indent was working
in 8-space tab stops, while now it knows we use 4-space tabs. So the
net result is that in about half the cases, such comments are placed
one tab stop left of before. This is better all around: it leaves
more room on the line for comment text, and it means that in such
cases the comment uniformly starts at the next 4-space tab stop after
the code, rather than sometimes one and sometimes two tabs after.
Also, ensure that comments following #endif are indented the same
as comments following other preprocessor commands such as #else.
That inconsistency turns out to have been self-inflicted damage
from a poorly-thought-through post-indent "fixup" in pgindent.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
|
|
|
#endif /* PATHS_H */
|