Commit 8b109ebf authored by Tom Lane's avatar Tom Lane

Teach planner to convert simple UNION ALL subqueries into append relations,

thereby sharing code with the inheritance case.  This puts the UNION-ALL-view
approach to partitioned tables on par with inheritance, so far as constraint
exclusion is concerned: it works either way.  (Still need to update the docs
to say so.)  The definition of "simple UNION ALL" is a little simpler than
I would like --- basically the union arms can only be SELECT * FROM foo
--- but it's good enough for partitioned-table cases.
parent a25b1dec
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.140 2006/01/31 21:39:23 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.141 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -42,6 +42,8 @@ int geqo_threshold;
static void set_base_rel_pathlists(PlannerInfo *root);
static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
......@@ -133,7 +135,6 @@ set_base_rel_pathlists(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *rel = root->simple_rel_array[rti];
RangeTblEntry *rte;
/* there may be empty slots corresponding to non-baserel RTEs */
if (rel == NULL)
......@@ -145,33 +146,44 @@ set_base_rel_pathlists(PlannerInfo *root)
if (rel->reloptkind != RELOPT_BASEREL)
continue;
rte = rt_fetch(rti, root->parse->rtable);
set_rel_pathlist(root, rel, rti,
rt_fetch(rti, root->parse->rtable));
}
}
if (rte->inh)
{
/* 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 separate plan for it */
set_function_pathlist(root, rel, rte);
}
else
{
/* Plain relation */
set_plain_rel_pathlist(root, rel, rte);
}
/*
* set_rel_pathlist
* Build access paths for a base relation
*/
static void
set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte)
{
if (rte->inh)
{
/* 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 separate plan for it */
set_function_pathlist(root, rel, rte);
}
else
{
/* Plain relation */
Assert(rel->rtekind == RTE_RELATION);
set_plain_rel_pathlist(root, rel, rte);
}
#ifdef OPTIMIZER_DEBUG
debug_print_rel(root, rel);
debug_print_rel(root, rel);
#endif
}
}
/*
......@@ -181,9 +193,6 @@ set_base_rel_pathlists(PlannerInfo *root)
static void
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Assert(rel->rtekind == RTE_RELATION);
Assert(!rte->inh);
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
......@@ -265,6 +274,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
int childRTindex;
RelOptInfo *childrel;
RangeTblEntry *childrte;
Path *childpath;
ListCell *parentvars;
ListCell *childvars;
......@@ -346,10 +356,20 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/*
* Compute the child's access paths, and save the cheapest.
*
* 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_plain_rel_pathlist(root, childrel, childrte);
set_rel_pathlist(root, childrel, childRTindex, childrte);
subpaths = lappend(subpaths, childrel->cheapest_total_path);
childpath = childrel->cheapest_total_path;
if (IsA(childpath, AppendPath))
subpaths = list_concat(subpaths,
((AppendPath *) childpath)->subpaths);
else
subpaths = lappend(subpaths, childpath);
/*
* Propagate size information from the child back to the parent. For
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.99 2006/01/31 21:39:23 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.100 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -20,7 +20,6 @@
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/prep.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
......@@ -856,8 +855,6 @@ join_before_append(PlannerInfo *root,
int childRTindex;
RelOptInfo *childrel;
Path *bestinnerjoin;
Relids joinrelids;
Relids *save_attr_needed;
RelOptInfo *this_joinrel;
List *this_restrictlist;
......@@ -899,27 +896,9 @@ join_before_append(PlannerInfo *root,
* in joinrels.c, it provides necessary context for the Path,
* such as properly-translated target and quals lists.
*/
joinrelids = bms_copy(joinrel->relids);
joinrelids = bms_del_member(joinrelids, parentRTindex);
joinrelids = bms_add_member(joinrelids, childRTindex);
/*
* Kluge: temporarily adjust the outer rel's attr_needed info so
* that it references the member rel instead of the appendrel.
* This is needed to build the correct targetlist for the joinrel.
*/
save_attr_needed = outerrel->attr_needed;
outerrel->attr_needed =
adjust_other_rel_attr_needed(outerrel, appinfo,
outerrel->min_attr,
outerrel->max_attr);
this_joinrel = build_join_rel(root, joinrelids, outerrel, childrel,
jointype, &this_restrictlist);
/* Now we can undo the hack on attr_needed */
pfree(outerrel->attr_needed);
outerrel->attr_needed = save_attr_needed;
this_joinrel = translate_join_rel(root, joinrel, appinfo,
outerrel, childrel, jointype,
&this_restrictlist);
/* Build Path for join and add to result list */
append_paths = lappend(append_paths,
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.197 2006/01/31 21:39:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.198 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -222,7 +222,7 @@ subquery_planner(Query *parse, double tuple_fraction,
* this query.
*/
parse->jointree = (FromExpr *)
pull_up_subqueries(root, (Node *) parse->jointree, false);
pull_up_subqueries(root, (Node *) parse->jointree, false, false);
/*
* Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
......
This diff is collapsed.
......@@ -4,13 +4,17 @@
* Routines to plan set-operation queries. The filename is a leftover
* from a time when only UNIONs were implemented.
*
* There are two code paths in the planner for set-operation queries.
* If a subquery consists entirely of simple UNION ALL operations, it
* is converted into an "append relation". Otherwise, it is handled
* by the general code in this module (plan_set_operations and its
* subroutines). There is some support code here for the append-relation
* case, but most of the heavy lifting for that is done elsewhere,
* notably in prepjointree.c and allpaths.c.
*
* There is also some code here to support planning of queries that use
* inheritance (SELECT FROM foo*). Although inheritance is radically
* different from set operations as far as the parser representation of
* a query is concerned, we try to handle it identically to the UNION ALL
* case during planning: both are converted to "append rels". (Note that
* UNION ALL is special-cased: other kinds of set operations go through
* a completely different code path.)
* inheritance (SELECT FROM foo*). Inheritance trees are converted into
* append relations, and thenceforth share code with the UNION ALL case.
*
*
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
......@@ -18,7 +22,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.129 2006/01/31 21:39:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.130 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -64,12 +68,13 @@ static List *generate_setop_tlist(List *colTypes, int flag,
static List *generate_append_tlist(List *colTypes, bool flag,
List *input_plans,
List *refnames_tlist);
static bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
Index rti);
static void make_translation_lists(Relation oldrelation, Relation newrelation,
Index newvarno,
List **col_mappings, List **translated_vars);
static void make_inh_translation_lists(Relation oldrelation,
Relation newrelation,
Index newvarno,
List **col_mappings,
List **translated_vars);
static Node *adjust_appendrel_attrs_mutator(Node *node,
AppendRelInfo *context);
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
......@@ -659,41 +664,6 @@ generate_append_tlist(List *colTypes, bool flag,
return tlist;
}
/*
* Does tlist have same datatypes as requested colTypes?
*
* Resjunk columns are ignored if junkOK is true; otherwise presence of
* a resjunk column will always cause a 'false' result.
*/
static bool
tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK)
{
ListCell *l;
ListCell *curColType = list_head(colTypes);
foreach(l, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resjunk)
{
if (!junkOK)
return false;
}
else
{
if (curColType == NULL)
return false;
if (exprType((Node *) tle->expr) != lfirst_oid(curColType))
return false;
curColType = lnext(curColType);
}
}
if (curColType != NULL)
return false;
return true;
}
/*
* find_all_inheritors -
......@@ -896,9 +866,9 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
appinfo->child_relid = childRTindex;
appinfo->parent_reltype = oldrelation->rd_rel->reltype;
appinfo->child_reltype = newrelation->rd_rel->reltype;
make_translation_lists(oldrelation, newrelation, childRTindex,
&appinfo->col_mappings,
&appinfo->translated_vars);
make_inh_translation_lists(oldrelation, newrelation, childRTindex,
&appinfo->col_mappings,
&appinfo->translated_vars);
appinfo->parent_reloid = parentOID;
appinfos = lappend(appinfos, appinfo);
......@@ -933,7 +903,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
* make_translation_lists
* make_inh_translation_lists
* Build the lists of translations from parent Vars to child Vars for
* an inheritance child. We need both a column number mapping list
* and a list of Vars representing the child columns.
......@@ -941,9 +911,9 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
* For paranoia's sake, we match type as well as attribute name.
*/
static void
make_translation_lists(Relation oldrelation, Relation newrelation,
Index newvarno,
List **col_mappings, List **translated_vars)
make_inh_translation_lists(Relation oldrelation, Relation newrelation,
Index newvarno,
List **col_mappings, List **translated_vars)
{
List *numbers = NIL;
List *vars = NIL;
......@@ -1123,8 +1093,18 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context)
}
else
{
/* XXX copy some code from ResolveNew */
Assert(false);/* not done yet */
/*
* Build a RowExpr containing the translated variables.
*/
RowExpr *rowexpr;
List *fields;
fields = (List *) copyObject(context->translated_vars);
rowexpr = makeNode(RowExpr);
rowexpr->args = fields;
rowexpr->row_typeid = var->vartype;
rowexpr->row_format = COERCE_IMPLICIT_CAST;
return (Node *) rowexpr;
}
}
/* system attributes don't need any other translation */
......@@ -1337,45 +1317,6 @@ adjust_appendrel_attr_needed(RelOptInfo *oldrel, AppendRelInfo *appinfo,
return new_attr_needed;
}
/*
* adjust_other_rel_attr_needed
* Adjust an attr_needed[] array to reference a member rel instead of
* the original appendrel
*
* This is exactly like adjust_appendrel_attr_needed except that we disregard
* appinfo->col_mappings and instead assume that the mapping of user
* attributes is one-to-one. This is appropriate for generating an attr_needed
* array that describes another relation to be joined with a member rel.
*/
Relids *
adjust_other_rel_attr_needed(RelOptInfo *oldrel, AppendRelInfo *appinfo,
AttrNumber new_min_attr, AttrNumber new_max_attr)
{
Relids *new_attr_needed;
Index parent_relid = appinfo->parent_relid;
Index child_relid = appinfo->child_relid;
int parent_attr;
/* Create empty result array */
Assert(new_min_attr <= oldrel->min_attr);
Assert(new_max_attr >= oldrel->max_attr);
new_attr_needed = (Relids *)
palloc0((new_max_attr - new_min_attr + 1) * sizeof(Relids));
/* Process user attributes and system attributes */
for (parent_attr = oldrel->min_attr; parent_attr <= oldrel->max_attr;
parent_attr++)
{
Relids attrneeded;
attrneeded = oldrel->attr_needed[parent_attr - oldrel->min_attr];
attrneeded = adjust_relid_set(attrneeded,
parent_relid, child_relid);
new_attr_needed[parent_attr - new_min_attr] = attrneeded;
}
return new_attr_needed;
}
/*
* Adjust the targetlist entries of an inherited UPDATE operation
*
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.75 2006/01/31 21:39:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.76 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -18,6 +18,7 @@
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/plancat.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
......@@ -570,3 +571,144 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
}
}
}
/*
* translate_join_rel
* Returns relation entry corresponding to the union of two given rels,
* creating a new relation entry if none already exists. This is used
* when one of the inputs is an append child relation. In addition to
* data about the input rels themselves, the corresponding joinrel for
* the append parent relation must be provided, plus the AppendRelInfo
* showing the parent-to-child translation.
*
* The reason for having this code, instead of just applying build_join_rel,
* is that we must have corresponding tlist orderings for all joinrels that
* are involved in an Append plan. So we generate the tlist for joinrels
* involving append child relations by translating the parent joinrel's tlist,
* rather than examining the input relations directly. (Another reason for
* doing it this way is that the base relation attr_needed info in relations
* being joined to the appendrel doesn't refer to the append child rel, but
* the append parent, and so couldn't be used directly anyway.) Otherwise
* this is exactly like build_join_rel.
*/
RelOptInfo *
translate_join_rel(PlannerInfo *root,
RelOptInfo *oldjoinrel,
AppendRelInfo *appinfo,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel,
JoinType jointype,
List **restrictlist_ptr)
{
RelOptInfo *joinrel;
Relids joinrelids;
List *restrictlist;
/*
* Construct the Relids set for the translated joinrel, and see if
* we've already built it.
*/
joinrelids = bms_copy(oldjoinrel->relids);
joinrelids = bms_del_member(joinrelids, appinfo->parent_relid);
joinrelids = bms_add_member(joinrelids, appinfo->child_relid);
joinrel = find_join_rel(root, joinrelids);
if (joinrel)
{
/*
* Yes, so we only need to figure the restrictlist for this particular
* pair of component relations.
*/
bms_free(joinrelids);
if (restrictlist_ptr)
*restrictlist_ptr = build_joinrel_restrictlist(root,
joinrel,
outer_rel,
inner_rel,
jointype);
return joinrel;
}
/*
* Nope, so make one.
*/
joinrel = makeNode(RelOptInfo);
joinrel->reloptkind = RELOPT_JOINREL;
joinrel->relids = joinrelids;
joinrel->rows = 0;
joinrel->width = 0;
joinrel->reltargetlist = NIL;
joinrel->pathlist = NIL;
joinrel->cheapest_startup_path = NULL;
joinrel->cheapest_total_path = NULL;
joinrel->cheapest_unique_path = NULL;
joinrel->relid = 0; /* indicates not a baserel */
joinrel->rtekind = RTE_JOIN;
joinrel->min_attr = 0;
joinrel->max_attr = 0;
joinrel->attr_needed = NULL;
joinrel->attr_widths = NULL;
joinrel->indexlist = NIL;
joinrel->pages = 0;
joinrel->tuples = 0;
joinrel->subplan = NULL;
joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0;
joinrel->joininfo = NIL;
joinrel->index_outer_relids = NULL;
joinrel->index_inner_paths = NIL;
/*
* Make the tlist by translating oldjoinrel's tlist, to ensure they
* are in compatible orders. Since we don't call build_joinrel_tlist,
* we need another way to set the rel width; for the moment, just
* assume it is the same as oldjoinrel. (The correct value may well be
* less, but it's not clear it's worth the trouble to get it right.)
*/
joinrel->reltargetlist = (List *)
adjust_appendrel_attrs((Node *) oldjoinrel->reltargetlist,
appinfo);
joinrel->width = oldjoinrel->width;
/*
* Construct restrict and join clause lists for the new joinrel. (The
* caller might or might not need the restrictlist, but I need it anyway
* for set_joinrel_size_estimates().)
*/
restrictlist = build_joinrel_restrictlist(root,
joinrel,
outer_rel,
inner_rel,
jointype);
if (restrictlist_ptr)
*restrictlist_ptr = restrictlist;
build_joinrel_joinlist(joinrel, outer_rel, inner_rel);
/*
* Set estimates of the joinrel's size.
*/
set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
jointype, restrictlist);
/*
* Add the joinrel to the query's joinrel list, and store it into the
* auxiliary hashtable if there is one. NB: GEQO requires us to append
* the new joinrel to the end of the list!
*/
root->join_rel_list = lappend(root->join_rel_list, joinrel);
if (root->join_rel_hash)
{
JoinHashEntry *hentry;
bool found;
hentry = (JoinHashEntry *) hash_search(root->join_rel_hash,
&(joinrel->relids),
HASH_ENTER,
&found);
Assert(!found);
hentry->join_rel = joinrel;
}
return joinrel;
}
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.70 2005/10/15 02:49:21 momjian Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.71 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -167,3 +167,39 @@ get_sortgrouplist_exprs(List *sortClauses, List *targetList)
}
return result;
}
/*
* Does tlist have same output datatypes as listed in colTypes?
*
* Resjunk columns are ignored if junkOK is true; otherwise presence of
* a resjunk column will always cause a 'false' result.
*/
bool
tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK)
{
ListCell *l;
ListCell *curColType = list_head(colTypes);
foreach(l, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resjunk)
{
if (!junkOK)
return false;
}
else
{
if (curColType == NULL)
return false; /* tlist longer than colTypes */
if (exprType((Node *) tle->expr) != lfirst_oid(curColType))
return false;
curColType = lnext(curColType);
}
}
if (curColType != NULL)
return false; /* tlist shorter than colTypes */
return true;
}
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.64 2006/01/31 21:39:25 tgl Exp $
* $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.65 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -95,5 +95,12 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root,
RelOptInfo *inner_rel,
JoinType jointype,
List **restrictlist_ptr);
extern RelOptInfo *translate_join_rel(PlannerInfo *root,
RelOptInfo *oldjoinrel,
AppendRelInfo *appinfo,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel,
JoinType jointype,
List **restrictlist_ptr);
#endif /* PATHNODE_H */
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.54 2006/01/31 21:39:25 tgl Exp $
* $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.55 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -23,7 +23,7 @@
*/
extern Node *pull_up_IN_clauses(PlannerInfo *root, Node *node);
extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode,
bool below_outer_join);
bool below_outer_join, bool append_rel_member);
extern void reduce_outer_joins(PlannerInfo *root);
extern Relids get_relids_in_jointree(Node *jtnode);
extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
......@@ -55,9 +55,4 @@ extern Relids *adjust_appendrel_attr_needed(RelOptInfo *oldrel,
AttrNumber new_min_attr,
AttrNumber new_max_attr);
extern Relids *adjust_other_rel_attr_needed(RelOptInfo *oldrel,
AppendRelInfo *appinfo,
AttrNumber new_min_attr,
AttrNumber new_max_attr);
#endif /* PREP_H */
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.42 2005/04/06 16:34:07 tgl Exp $
* $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.43 2006/02/03 21:08:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -29,4 +29,6 @@ extern Node *get_sortgroupclause_expr(SortClause *sortClause,
extern List *get_sortgrouplist_exprs(List *sortClauses,
List *targetList);
extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
#endif /* TLIST_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment