diff options
author | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
---|---|---|
committer | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
commit | d31084e9d1118b25fd16580d9d8c2924b5740dff (patch) | |
tree | 3179e66307d54df9c7b966543550e601eb55e668 /src/backend/optimizer | |
download | postgresql-PG95-1_01.tar.gz postgresql-PG95-1_01.zip |
Postgres95 1.01 Distribution - Virgin SourcesPG95-1_01
Diffstat (limited to 'src/backend/optimizer')
55 files changed, 15569 insertions, 0 deletions
diff --git a/src/backend/optimizer/Makefile.inc b/src/backend/optimizer/Makefile.inc new file mode 100644 index 00000000000..bd453d69645 --- /dev/null +++ b/src/backend/optimizer/Makefile.inc @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the optimizer module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/optimizer/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ +# +#------------------------------------------------------------------------- + +optdir=$(CURDIR)/optimizer +VPATH:=$(VPATH):$(optdir):\ + $(optdir)/path:$(optdir)/prep:$(optdir)/util:$(optdir)/plan + +SUBSRCS= +include $(optdir)/path/Makefile.inc +include $(optdir)/prep/Makefile.inc +include $(optdir)/util/Makefile.inc +include $(optdir)/plan/Makefile.inc +SRCS_OPTIMIZER:= $(SUBSRCS) + +HEADERS+= clauseinfo.h clauses.h cost.h internal.h joininfo.h keys.h \ + ordering.h pathnode.h paths.h plancat.h planmain.h \ + planner.h prep.h tlist.h var.h xfunc.h + + diff --git a/src/backend/optimizer/clauseinfo.h b/src/backend/optimizer/clauseinfo.h new file mode 100644 index 00000000000..c1c100d8fc6 --- /dev/null +++ b/src/backend/optimizer/clauseinfo.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * clauseinfo.h-- + * prototypes for clauseinfo.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: clauseinfo.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CLAUSEINFO_H +#define CLAUSEINFO_H + +extern bool valid_or_clause(CInfo *clauseinfo); +extern List *get_actual_clauses(List *clauseinfo_list); +extern void get_relattvals(List *clauseinfo_list, List **attnos, + List **values, List **flags); +extern void get_joinvars(Oid relid, List *clauseinfo_list, + List **attnos, List **values, List **flags); +extern List *get_opnos(List *clauseinfo_list); + +#endif /* CLAUSEINFO_H */ diff --git a/src/backend/optimizer/clauses.h b/src/backend/optimizer/clauses.h new file mode 100644 index 00000000000..2289223b3e8 --- /dev/null +++ b/src/backend/optimizer/clauses.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * clauses.h-- + * prototypes for clauses.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: clauses.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CLAUSES_H +#define CLAUSES_H + +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" + +extern Expr *make_clause(int type, Node *oper, List *args); +extern bool is_opclause(Node *clause); +extern Expr *make_opclause(Oper *op, Var *leftop, Var *rightop); +extern Var *get_leftop(Expr *clause); +extern Var *get_rightop(Expr *clause); + +extern bool agg_clause(Node *clause); + +extern bool is_funcclause(Node *clause); +extern Expr *make_funcclause(Func *func, List *funcargs); + +extern bool or_clause(Node *clause); +extern Expr *make_orclause(List *orclauses); + +extern bool not_clause(Node *clause); +extern Expr *make_notclause(Expr *notclause); +extern Expr *get_notclausearg(Expr *notclause); + +extern bool and_clause(Node *clause); +extern Expr *make_andclause(List *andclauses); + +extern List *pull_constant_clauses(List *quals, List **constantQual); +extern void clause_relids_vars(Node *clause, List **relids, List **vars); +extern int NumRelids(Node *clause); +extern bool contains_not(Node *clause); +extern bool join_clause_p(Node *clause); +extern bool qual_clause_p(Node *clause); +extern void fix_opid(Node *clause); +extern List *fix_opids(List *clauses); +extern void get_relattval(Node *clause, int *relid, + AttrNumber *attno, Datum *constval, int *flag); +extern void get_rels_atts(Node *clause, int *relid1, + AttrNumber *attno1, int *relid2, AttrNumber *attno2); +extern void CommuteClause(Node *clause); + +#endif /* CLAUSES_H */ diff --git a/src/backend/optimizer/cost.h b/src/backend/optimizer/cost.h new file mode 100644 index 00000000000..041dcbbe8e8 --- /dev/null +++ b/src/backend/optimizer/cost.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * cost.h-- + * prototypes for costsize.c and clausesel.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: cost.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef COST_H +#define COST_H + +/* + * prototypes for costsize.c-- + * routines to compute costs and sizes + */ +extern bool _enable_seqscan_; +extern bool _enable_indexscan_; +extern bool _enable_sort_; +extern bool _enable_hash_; +extern bool _enable_nestloop_; +extern bool _enable_mergesort_; +extern bool _enable_hashjoin_; + +extern Cost cost_seqscan(int relid, int relpages, int reltuples); +extern Cost cost_index(Oid indexid, int expected_indexpages, Cost selec, + int relpages, int reltuples, int indexpages, + int indextuples, bool is_injoin); +extern Cost cost_sort(List *keys, int tuples, int width, bool noread); +extern Cost cost_result(int tuples, int width); +extern Cost cost_nestloop(Cost outercost, Cost innercost, int outertuples, + int innertuples, int outerpages, bool is_indexjoin); +extern Cost cost_mergesort(Cost outercost, Cost innercost, + List *outersortkeys, List *innersortkeys, + int outersize, int innersize, int outerwidth, int innerwidth); +extern Cost cost_hashjoin(Cost outercost, Cost innercost, List *outerkeys, + List *innerkeys, int outersize, int innersize, + int outerwidth, int innerwidth); +extern int compute_rel_size(Rel *rel); +extern int compute_rel_width(Rel *rel); +extern int compute_targetlist_width(List *targetlist); +extern int compute_joinrel_size(JoinPath *joinpath); +extern int page_size(int tuples, int width); + +/* + * prototypes for fuctions in clausesel.h-- + * routines to compute clause selectivities + */ +extern void set_clause_selectivities(List *clauseinfo_list, Cost new_selectivity); +extern Cost product_selec(List *clauseinfo_list); +extern void set_rest_relselec(Query *root, List *rel_list); +extern void set_rest_selec(Query *root,List *clauseinfo_list); +extern Cost compute_clause_selec(Query *root, + Node *clause, List *or_selectivities); + +#endif /* COST_H */ diff --git a/src/backend/optimizer/internal.h b/src/backend/optimizer/internal.h new file mode 100644 index 00000000000..c8597a537e7 --- /dev/null +++ b/src/backend/optimizer/internal.h @@ -0,0 +1,92 @@ +/*------------------------------------------------------------------------- + * + * internal.h-- + * Definitions required throughout the query optimizer. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: internal.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef INTERNAL_H +#define INTERNAL_H + +/* + * ---------- SHARED MACROS + * + * Macros common to modules for creating, accessing, and modifying + * query tree and query plan components. + * Shared with the executor. + * + */ + +#include "nodes/nodes.h" +#include "nodes/primnodes.h" +#include "nodes/plannodes.h" +#include "parser/parsetree.h" +#include "nodes/relation.h" +#include "catalog/pg_index.h" /* for INDEX_MAX_KEYS */ +#include "utils/syscache.h" /* for SearchSysCacheGetAttribute, etc. */ + +/* + * System-dependent tuning constants + * + */ +#define _CPU_PAGE_WEIGHT_ 0.065 /* CPU-to-page cost weighting factor */ +#define _PAGE_SIZE_ 8192 /* BLCKSZ (from ../h/bufmgr.h) */ +#define _MAX_KEYS_ INDEX_MAX_KEYS /* maximum number of keys in an index */ +#define _TID_SIZE_ 6 /* sizeof(itemid) (from ../h/itemid.h) */ + +/* + * Size estimates + * + */ + +/* The cost of sequentially scanning a materialized temporary relation + */ +#define _TEMP_SCAN_COST_ 10 + +/* The number of pages and tuples in a materialized relation + */ +#define _TEMP_RELATION_PAGES_ 1 +#define _TEMP_RELATION_TUPLES_ 10 + +/* The length of a variable-length field in bytes + */ +#define _DEFAULT_ATTRIBUTE_WIDTH_ (2 * _TID_SIZE_) + +/* + * Flags and identifiers + * + */ + +/* Identifier for (sort) temp relations */ +/* used to be -1 */ +#define _TEMP_RELATION_ID_ InvalidOid + +/* Identifier for invalid relation OIDs and attribute numbers for use by + * selectivity functions + */ +#define _SELEC_VALUE_UNKNOWN_ -1 + +/* Flag indicating that a clause constant is really a parameter (or other + * non-constant?), a non-parameter, or a constant on the right side + * of the clause. + */ +#define _SELEC_NOT_CONSTANT_ 0 +#define _SELEC_IS_CONSTANT_ 1 +#define _SELEC_CONSTANT_LEFT_ 0 +#define _SELEC_CONSTANT_RIGHT_ 2 + +#define TOLERANCE 0.000001 + +#define FLOAT_EQUAL(X,Y) ((X) - (Y) < TOLERANCE) +#define FLOAT_IS_ZERO(X) (FLOAT_EQUAL(X,0.0)) + +extern int BushyPlanFlag; +/* #define deactivate_joininfo(joininfo) joininfo->inactive=true*/ +/*#define joininfo_inactive(joininfo) joininfo->inactive */ + +#endif /* INTERNAL_H */ diff --git a/src/backend/optimizer/joininfo.h b/src/backend/optimizer/joininfo.h new file mode 100644 index 00000000000..beda9a67de0 --- /dev/null +++ b/src/backend/optimizer/joininfo.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * joininfo.h-- + * prototypes for joininfo.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: joininfo.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef JOININFO_H +#define JOININFO_H + +extern JInfo *joininfo_member(List *join_relids, List *joininfo_list); +extern JInfo *find_joininfo_node(Rel *this_rel, List *join_relids); +extern Var *other_join_clause_var(Var *var, Expr *clause); + +#endif /* JOININFO_H */ diff --git a/src/backend/optimizer/keys.h b/src/backend/optimizer/keys.h new file mode 100644 index 00000000000..ac579089f51 --- /dev/null +++ b/src/backend/optimizer/keys.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * keys.h-- + * prototypes for keys.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: keys.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef KEYS_H +#define KEYS_H + +extern bool match_indexkey_operand(int indexkey, Var *operand, Rel *rel); +extern bool equal_indexkey_var(int index_key, Var *var); +extern Var *extract_subkey(JoinKey *jk, int which_subkey); +extern bool samekeys(List *keys1, List *keys2); +extern List *collect_index_pathkeys(int *index_keys, List *tlist); + +#endif /* KEYS_H */ diff --git a/src/backend/optimizer/ordering.h b/src/backend/optimizer/ordering.h new file mode 100644 index 00000000000..0b598fb71a9 --- /dev/null +++ b/src/backend/optimizer/ordering.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * ordering.h-- + * prototypes for ordering.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: ordering.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ORDERING_H +#define ORDERING_H + +extern bool equal_path_path_ordering(PathOrder *path_ordering1, + PathOrder *path_ordering2); +extern bool equal_path_merge_ordering(Oid *path_ordering, + MergeOrder *merge_ordering); +extern bool equal_merge_merge_ordering(MergeOrder *merge_ordering1, + MergeOrder *merge_ordering2); +extern bool equal_sortops_order(Oid *ordering1, Oid *ordering2); + +#endif /* ORDERING_H */ diff --git a/src/backend/optimizer/path/Makefile.inc b/src/backend/optimizer/path/Makefile.inc new file mode 100644 index 00000000000..6bb014b0a90 --- /dev/null +++ b/src/backend/optimizer/path/Makefile.inc @@ -0,0 +1,21 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for optimizer/path +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS= allpaths.c clausesel.c costsize.c hashutils.c indxpath.c \ + joinpath.c joinrels.c joinutils.c mergeutils.c orindxpath.c \ + prune.c + +# not ready yet: predmig.c xfunc.c + + + diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c new file mode 100644 index 00000000000..0ed2139095d --- /dev/null +++ b/src/backend/optimizer/path/allpaths.c @@ -0,0 +1,351 @@ +/*------------------------------------------------------------------------- + * + * allpaths.c-- + * Routines to find possible search paths for processing a query + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> + +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" + +#include "optimizer/internal.h" + +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/clauses.h" +#include "optimizer/xfunc.h" +#include "optimizer/cost.h" + +#include "commands/creatinh.h" + +static void find_rel_paths(Query *root, List *rels); +static List *find_join_paths(Query *root, List *outer_rels, int levels_left); + +/* + * find-paths-- + * Finds all possible access paths for executing a query, returning the + * top level list of relation entries. + * + * 'rels' is the list of single relation entries appearing in the query + */ +List * +find_paths(Query *root, List *rels) +{ + int levels_left; + + /* + * Set the number of join (not nesting) levels yet to be processed. + */ + levels_left = length(rels); + + if (levels_left <= 0) + return NIL; + + /* + * Find the base relation paths. + */ + find_rel_paths(root, rels); + + if (levels_left <= 1) { + /* + * Unsorted single relation, no more processing is required. + */ + return (rels); + }else { + /* + * this means that joins or sorts are required. + * set selectivities of clauses that have not been set + * by an index. + */ + set_rest_relselec(root, rels); + + return(find_join_paths(root, rels, levels_left-1)); + } +} + +/* + * find-rel-paths-- + * Finds all paths available for scanning each relation entry in + * 'rels'. Sequential scan and any available indices are considered + * if possible(indices are not considered for lower nesting levels). + * All unique paths are attached to the relation's 'pathlist' field. + * + * MODIFIES: rels + */ +static void +find_rel_paths(Query *root, List *rels) +{ + List *temp; + Rel *rel; + List *lastpath; + + foreach(temp, rels) { + List *sequential_scan_list; + List *rel_index_scan_list; + List *or_index_scan_list; + + rel = (Rel *)lfirst(temp); + sequential_scan_list = lcons(create_seqscan_path(rel), + NIL); + + rel_index_scan_list = + find_index_paths(root, + rel, + find_relation_indices(root,rel), + rel->clauseinfo, + rel->joininfo); + + or_index_scan_list = + create_or_index_paths(root, rel, rel->clauseinfo); + + rel->pathlist = add_pathlist(rel, + sequential_scan_list, + append(rel_index_scan_list, + or_index_scan_list)); + + /* The unordered path is always the last in the list. + * If it is not the cheapest path, prune it. + */ + lastpath = rel->pathlist; + while(lnext(lastpath)!=NIL) + lastpath=lnext(lastpath); + prune_rel_path(rel, (Path*)lfirst(lastpath)); + /* + * if there is a qualification of sequential scan the selec. + * value is not set -- so set it explicitly -- Sunita + */ + set_rest_selec(root, rel->clauseinfo); + rel->size = compute_rel_size(rel); + rel->width = compute_rel_width(rel); + } + return; +} + +/* + * find-join-paths-- + * Find all possible joinpaths for a query by successively finding ways + * to join single relations into join relations. + * + * if BushyPlanFlag is set, bushy tree plans will be generated: + * Find all possible joinpaths(bushy trees) for a query by systematically + * finding ways to join relations(both original and derived) together. + * + * 'outer-rels' is the current list of relations for which join paths + * are to be found, i.e., he current list of relations that + * have already been derived. + * 'levels-left' is the current join level being processed, where '1' is + * the "last" level + * + * Returns the final level of join relations, i.e., the relation that is + * the result of joining all the original relations togehter. + */ +static List * +find_join_paths(Query *root, List *outer_rels, int levels_left) +{ + List *x; + List *new_rels; + Rel *rel; + + /* + * Determine all possible pairs of relations to be joined at this level. + * Determine paths for joining these relation pairs and modify 'new-rels' + * accordingly, then eliminate redundant join relations. + */ + new_rels = find_join_rels(root, outer_rels); + + find_all_join_paths(root, new_rels); + + new_rels = prune_joinrels(new_rels); + +#if 0 + /* + ** for each expensive predicate in each path in each distinct rel, + ** consider doing pullup -- JMH + */ + if (XfuncMode != XFUNC_NOPULL && XfuncMode != XFUNC_OFF) + foreach(x, new_rels) + xfunc_trypullup((Rel*)lfirst(x)); +#endif + + prune_rel_paths(new_rels); + + if(BushyPlanFlag) { + /* + * In case of bushy trees + * if there is still a join between a join relation and another + * relation, add a new joininfo that involves the join relation + * to the joininfo list of the other relation + */ + add_new_joininfos(root, new_rels,outer_rels); + } + + foreach(x, new_rels) { + rel = (Rel*)lfirst(x); + rel->size = compute_rel_size(rel); + rel->width = compute_rel_width(rel); + +/*#define OPTIMIZER_DEBUG*/ +#ifdef OPTIMIZER_DEBUG + printf("levels left: %d\n", levels_left); + debug_print_rel(root, rel); +#endif + } + + if(BushyPlanFlag) { + /* + * prune rels that have been completely incorporated into + * new join rels + */ + outer_rels = prune_oldrels(outer_rels); + /* + * merge join rels if then contain the same list of base rels + */ + outer_rels = merge_joinrels(new_rels,outer_rels); + root->join_relation_list_ = outer_rels; + } + else { + root->join_relation_list_ = new_rels; + } + + if(levels_left == 1) { + if(BushyPlanFlag) + return(final_join_rels(outer_rels)); + else + return(new_rels); + } else { + if(BushyPlanFlag) + return(find_join_paths(root, outer_rels, levels_left - 1)); + else + return(find_join_paths(root, new_rels, levels_left - 1)); + } +} + +/***************************************************************************** + * + *****************************************************************************/ + +static void +print_joinclauses(Query *root, List *clauses) +{ + List *l; + extern void print_expr(Node *expr, List *rtable); /* in print.c */ + + foreach(l, clauses) { + CInfo *c = lfirst(l); + + print_expr((Node*)c->clause, root->rtable); + if (lnext(l)) printf(" "); + } +} + +void +print_path(Query *root, Path *path, int indent) +{ + char *ptype = NULL; + JoinPath *jp; + bool join; + int i; + + for(i=0; i < indent; i++) + printf("\t"); + + switch(nodeTag(path)) { + case T_Path: + ptype = "SeqScan"; join=false; break; + case T_IndexPath: + ptype = "IdxScan"; join=false; break; + case T_JoinPath: + ptype = "Nestloop"; join=true; break; + case T_MergePath: + ptype = "MergeJoin"; join=true; break; + case T_HashPath: + ptype = "HashJoin"; join=true; break; + default: + break; + } + if (join) { + int size = path->parent->size; + jp = (JoinPath*)path; + printf("%s size=%d cost=%f\n", ptype, size, path->path_cost); + switch(nodeTag(path)) { + case T_MergePath: + case T_HashPath: + for(i=0; i < indent+1; i++) + printf("\t"); + printf(" clauses=("); + print_joinclauses(root, + ((JoinPath*)path)->pathclauseinfo); + printf(")\n"); + + if (nodeTag(path)==T_MergePath) { + MergePath *mp = (MergePath*)path; + if (mp->outersortkeys || mp->innersortkeys) { + for(i=0; i < indent+1; i++) + printf("\t"); + printf(" sortouter=%d sortinner=%d\n", + ((mp->outersortkeys)?1:0), + ((mp->innersortkeys)?1:0)); + } + } + break; + default: + break; + } + print_path(root, jp->outerjoinpath, indent+1); + print_path(root, jp->innerjoinpath, indent+1); + } else { + int size = path->parent->size; + int relid = lfirsti(path->parent->relids); + printf("%s(%d) size=%d cost=%f", + ptype, relid, size, path->path_cost); + + if (nodeTag(path)==T_IndexPath) { + List *k, *l; + + printf(" keys="); + foreach (k, path->keys) { + printf("("); + foreach (l, lfirst(k)) { + Var *var = lfirst(l); + printf("%d.%d", var->varnoold, var->varoattno); + if (lnext(l)) printf(", "); + } + printf(")"); + if (lnext(k)) printf(", "); + } + } + printf("\n"); + } +} + +#ifdef OPTIMIZER_DEBUG +static void +debug_print_rel(Query *root, Rel *rel) +{ + List *l; + + printf("("); + foreach(l, rel->relids) { + printf("%d ", lfirsti(l)); + } + printf("): size=%d width=%d\n", rel->size, rel->width); + + printf("\tpath list:\n"); + foreach (l, rel->pathlist) { + print_path(root, lfirst(l), 1); + } + printf("\tcheapest path:\n"); + print_path(root, rel->cheapestpath, 1); +} +#endif /* OPTIMIZER_DEBUG */ diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c new file mode 100644 index 00000000000..634e1130794 --- /dev/null +++ b/src/backend/optimizer/path/clausesel.c @@ -0,0 +1,331 @@ +/*------------------------------------------------------------------------- + * + * clausesel.c-- + * Routines to compute and set clause selectivities + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/cost.h" +#include "optimizer/plancat.h" + +#include "parser/parsetree.h" /* for getrelid() */ + +#include "catalog/pg_proc.h" +#include "catalog/pg_operator.h" + +#include "utils/elog.h" +#include "utils/lsyscache.h" + +static Cost compute_selec(Query *root, List *clauses, List *or_selectivities); + +/**************************************************************************** + * ROUTINES TO SET CLAUSE SELECTIVITIES + ****************************************************************************/ + +/* + * set_clause_selectivities - + * Sets the selectivity field for each of clause in 'clauseinfo-list' + * to 'new-selectivity'. If the selectivity has already been set, reset + * it only if the new one is better. + * + * Returns nothing of interest. + * + */ +void +set_clause_selectivities(List *clauseinfo_list, Cost new_selectivity) +{ + List *temp; + CInfo *clausenode; + Cost cost_clause; + + foreach (temp,clauseinfo_list) { + clausenode = (CInfo*)lfirst(temp); + cost_clause = clausenode->selectivity; + if ( FLOAT_IS_ZERO(cost_clause) || new_selectivity < cost_clause) { + clausenode->selectivity = new_selectivity; + } + } +} + +/* + * product_selec - + * Multiplies the selectivities of each clause in 'clauseinfo-list'. + * + * Returns a flonum corresponding to the selectivity of 'clauseinfo-list'. + */ +Cost +product_selec(List *clauseinfo_list) +{ + Cost result = 1.0; + if (clauseinfo_list!=NIL) { + List *xclausenode = NIL; + Cost temp; + + foreach(xclausenode,clauseinfo_list) { + temp = ((CInfo *)lfirst(xclausenode))->selectivity; + result = result * temp; + } + } + return(result); +} + +/* + * set_rest_relselec - + * Scans through clauses on each relation and assigns a selectivity to + * those clauses that haven't been assigned a selectivity by an index. + * + * Returns nothing of interest. + * MODIFIES: selectivities of the various rel's clauseinfo + * slots. + */ +void +set_rest_relselec(Query *root, List *rel_list) +{ + Rel *rel; + List *x; + + foreach (x,rel_list) { + rel = (Rel*)lfirst(x); + set_rest_selec(root, rel->clauseinfo); + } +} + +/* + * set_rest_selec - + * Sets the selectivity fields for those clauses within a single + * relation's 'clauseinfo-list' that haven't already been set. + * + * Returns nothing of interest. + * + */ +void +set_rest_selec(Query *root, List *clauseinfo_list) +{ + List *temp = NIL; + CInfo *clausenode = (CInfo*)NULL; + Cost cost_clause; + + foreach (temp,clauseinfo_list) { + clausenode = (CInfo*)lfirst(temp); + cost_clause = clausenode->selectivity; + + /* + * Check to see if the selectivity of this clause or any 'or' + * subclauses (if any) haven't been set yet. + */ + if (valid_or_clause(clausenode) || FLOAT_IS_ZERO(cost_clause)) { + clausenode->selectivity = + compute_clause_selec(root, + (Node*)clausenode->clause, + lcons(makeFloat(cost_clause), NIL)); + } + } +} + +/**************************************************************************** + * ROUTINES TO COMPUTE SELECTIVITIES + ****************************************************************************/ + +/* + * compute_clause_selec - + * Given a clause, this routine will compute the selectivity of the + * clause by calling 'compute_selec' with the appropriate parameters + * and possibly use that return value to compute the real selectivity + * of a clause. + * + * 'or-selectivities' are selectivities that have already been assigned + * to subclauses of an 'or' clause. + * + * Returns a flonum corresponding to the clause selectivity. + * + */ +Cost +compute_clause_selec(Query *root, Node *clause, List *or_selectivities) +{ + if (!is_opclause (clause)) { + /* if it's not an operator clause, then it is a boolean clause -jolly*/ + /* + * Boolean variables get a selectivity of 1/2. + */ + return(0.1); + } else if (not_clause (clause)) { + /* + * 'not' gets "1.0 - selectivity-of-inner-clause". + */ + return (1.000000 - compute_selec(root, + lcons(get_notclausearg((Expr*)clause), + NIL), + or_selectivities)); + } else if (or_clause(clause)) { + /* + * Both 'or' and 'and' clauses are evaluated as described in + * (compute_selec). + */ + return (compute_selec(root, + ((Expr*)clause)->args, or_selectivities)); + } else { + return(compute_selec(root, + lcons(clause,NIL),or_selectivities)); + } +} + +/* + * compute_selec - + * Computes the selectivity of a clause. + * + * If there is more than one clause in the argument 'clauses', then the + * desired selectivity is that of an 'or' clause. Selectivities for an + * 'or' clause such as (OR a b) are computed by finding the selectivity + * of a (s1) and b (s2) and computing s1+s2 - s1*s2. + * + * In addition, if the clause is an 'or' clause, individual selectivities + * may have already been assigned by indices to subclauses. These values + * are contained in the list 'or-selectivities'. + * + * Returns the clause selectivity as a flonum. + * + */ +static Cost +compute_selec(Query *root, List *clauses, List *or_selectivities) +{ + Cost s1 = 0; + List *clause = lfirst(clauses); + + if (clauses==NULL) { + s1 = 1.0; + } else if (IsA(clause,Param)) { + /* XXX How're we handling this before?? -ay */ + s1 = 1.0; + } else if (IsA(clause,Const)) { + s1 = ((bool) ((Const*) clause)->constvalue) ? 1.0 : 0.0; + } else if (IsA(clause,Var)) { + Oid relid = getrelid(((Var*)clause)->varno, + root->rtable); + + /* + * we have a bool Var. This is exactly equivalent to the clause: + * reln.attribute = 't' + * so we compute the selectivity as if that is what we have. The + * magic #define constants are a hack. I didn't want to have to + * do system cache look ups to find out all of that info. + */ + + s1 = restriction_selectivity(EqualSelectivityProcedure, + BooleanEqualOperator, + relid, + ((Var*)clause)->varoattno, + "t", + _SELEC_CONSTANT_RIGHT_); + } else if (or_selectivities) { + /* If s1 has already been assigned by an index, use that value. */ + List *this_sel = lfirst(or_selectivities); + + s1 = floatVal(this_sel); + } else if (is_funcclause((Node*)clause)) { + /* this isn't an Oper, it's a Func!! */ + /* + ** This is not an operator, so we guess at the selectivity. + ** THIS IS A HACK TO GET V4 OUT THE DOOR. FUNCS SHOULD BE + ** ABLE TO HAVE SELECTIVITIES THEMSELVES. + ** -- JMH 7/9/92 + */ + s1 = 0.1; + } else if (NumRelids((Node*) clause) == 1) { + /* ...otherwise, calculate s1 from 'clauses'. + * The clause is not a join clause, since there is + * only one relid in the clause. The clause + * selectivity will be based on the operator + * selectivity and operand values. + */ + Oid opno = ((Oper*)((Expr*)clause)->oper)->opno; + RegProcedure oprrest = get_oprrest(opno); + Oid relid; + int relidx; + AttrNumber attno; + Datum constval; + int flag; + + get_relattval((Node*)clause, &relidx, &attno, &constval, &flag); + relid = getrelid(relidx, root->rtable); + + /* if the oprrest procedure is missing for whatever reason, + use a selectivity of 0.5*/ + if (!oprrest) + s1 = (Cost) (0.5); + else + if (attno == InvalidAttrNumber) { + /* attno can be Invalid if the clause had a function in it, + i.e. WHERE myFunc(f) = 10 */ + /* this should be FIXED somehow to use function selectivity */ + s1 = (Cost) (0.5); + } else + s1 = (Cost) restriction_selectivity(oprrest, + opno, + relid, + attno, + (char *)constval, + flag); + + } else { + /* The clause must be a join clause. The clause + * selectivity will be based on the relations to be + * scanned and the attributes they are to be joined + * on. + */ + Oid opno = ((Oper*)((Expr*)clause)->oper)->opno; + RegProcedure oprjoin = get_oprjoin (opno); + int relid1, relid2; + AttrNumber attno1, attno2; + + get_rels_atts((Node*)clause, &relid1, &attno1, &relid2, &attno2); + relid1 = getrelid(relid1, root->rtable); + relid2 = getrelid(relid2, root->rtable); + + /* if the oprjoin procedure is missing for whatever reason, + use a selectivity of 0.5*/ + if (!oprjoin) + s1 = (Cost) (0.5); + else + s1 = (Cost) join_selectivity(oprjoin, + opno, + relid1, + attno1, + relid2, + attno2); + } + + /* A null clause list eliminates no tuples, so return a selectivity + * of 1.0. If there is only one clause, the selectivity is not + * that of an 'or' clause, but rather that of the single clause. + */ + + if (length (clauses) < 2) { + return(s1); + } else { + /* Compute selectivity of the 'or'ed subclauses. */ + /* Added check for taking lnext(NIL). -- JMH 3/9/92 */ + Cost s2; + + if (or_selectivities != NIL) + s2 = compute_selec(root, lnext(clauses), lnext(or_selectivities)); + else + s2 = compute_selec(root, lnext(clauses), NIL); + return(s1 + s2 - s1 * s2); + } +} + diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c new file mode 100644 index 00000000000..14bbb5f8e4f --- /dev/null +++ b/src/backend/optimizer/path/costsize.c @@ -0,0 +1,456 @@ +/*------------------------------------------------------------------------- + * + * costsize.c-- + * Routines to compute (and set) relation sizes and path costs + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> +#ifdef WIN32 +#include <float.h> +#include <limits.h> +#define MAXINT INT_MAX +#else +# if defined(PORTNAME_BSD44_derived) || defined(PORTNAME_bsdi) +# include <machine/limits.h> +# define MAXINT INT_MAX +# else +# include <values.h> +# endif /* !PORTNAME_BSD44_derived */ +#endif /* WIN32 */ + +#include "postgres.h" + +#include "nodes/relation.h" + +#include "optimizer/cost.h" +#include "optimizer/internal.h" +#include "optimizer/keys.h" +#include "optimizer/tlist.h" + +#include "storage/bufmgr.h" /* for BLCKSZ */ + +static int compute_attribute_width(TargetEntry *tlistentry); +static double base_log(double x, double b); + +int _disable_cost_ = 30000000; + +bool _enable_seqscan_ = true; +bool _enable_indexscan_ = true; +bool _enable_sort_ = true; +bool _enable_hash_ = true; +bool _enable_nestloop_ = true; +bool _enable_mergesort_ = true; +bool _enable_hashjoin_ = true; + +/* + * cost_seqscan-- + * Determines and returns the cost of scanning a relation sequentially. + * If the relation is a temporary to be materialized from a query + * embedded within a data field (determined by 'relid' containing an + * attribute reference), then a predetermined constant is returned (we + * have NO IDEA how big the result of a POSTQUEL procedure is going to + * be). + * + * disk = p + * cpu = *CPU-PAGE-WEIGHT* * t + * + * 'relid' is the relid of the relation to be scanned + * 'relpages' is the number of pages in the relation to be scanned + * (as determined from the system catalogs) + * 'reltuples' is the number of tuples in the relation to be scanned + * + * Returns a flonum. + * + */ +Cost +cost_seqscan(int relid, int relpages, int reltuples) +{ + Cost temp = 0; + + if ( !_enable_seqscan_ ) + temp += _disable_cost_; + + if (relid < 0) { + /* + * cost of sequentially scanning a materialized temporary relation + */ + temp += _TEMP_SCAN_COST_; + } else { + temp += relpages; + temp += _CPU_PAGE_WEIGHT_ * reltuples; + } + Assert(temp >= 0); + return(temp); +} + + +/* + * cost_index-- + * Determines and returns the cost of scanning a relation using an index. + * + * disk = expected-index-pages + expected-data-pages + * cpu = *CPU-PAGE-WEIGHT* * + * (expected-index-tuples + expected-data-tuples) + * + * 'indexid' is the index OID + * 'expected-indexpages' is the number of index pages examined in the scan + * 'selec' is the selectivity of the index + * 'relpages' is the number of pages in the main relation + * 'reltuples' is the number of tuples in the main relation + * 'indexpages' is the number of pages in the index relation + * 'indextuples' is the number of tuples in the index relation + * + * Returns a flonum. + * + */ +Cost +cost_index(Oid indexid, + int expected_indexpages, + Cost selec, + int relpages, + int reltuples, + int indexpages, + int indextuples, + bool is_injoin) +{ + Cost temp; + Cost temp2; + + temp = temp2 = (Cost) 0; + + if (!_enable_indexscan_ && !is_injoin) + temp += _disable_cost_; + + /* expected index relation pages */ + temp += expected_indexpages; + + /* about one base relation page */ + temp += Min(relpages,(int)ceil((double)selec*indextuples)); + + /* + * per index tuple + */ + temp2 += selec * indextuples; + temp2 += selec * reltuples; + + temp = temp + (_CPU_PAGE_WEIGHT_ * temp2); + Assert(temp >= 0); + return(temp); +} + +/* + * cost_sort-- + * Determines and returns the cost of sorting a relation by considering + * 1. the cost of doing an external sort: XXX this is probably too low + * disk = (p lg p) + * cpu = *CPU-PAGE-WEIGHT* * (t lg t) + * 2. the cost of reading the sort result into memory (another seqscan) + * unless 'noread' is set + * + * 'keys' is a list of sort keys + * 'tuples' is the number of tuples in the relation + * 'width' is the average tuple width in bytes + * 'noread' is a flag indicating that the sort result can remain on disk + * (i.e., the sort result is the result relation) + * + * Returns a flonum. + * + */ +Cost +cost_sort(List *keys, int tuples, int width, bool noread) +{ + Cost temp = 0; + int npages = page_size (tuples,width); + Cost pages = (Cost)npages; + Cost numTuples = tuples; + + if ( !_enable_sort_ ) + temp += _disable_cost_ ; + if (tuples == 0 || keys==NULL) + { + Assert(temp >= 0); + return(temp); + } + temp += pages * base_log((double)pages, (double)2.0); + + /* + * could be base_log(pages, NBuffers), but we are only doing 2-way merges + */ + temp += _CPU_PAGE_WEIGHT_ * + numTuples * base_log((double)pages,(double)2.0); + + if( !noread ) + temp = temp + cost_seqscan(_TEMP_RELATION_ID_, npages, tuples); + Assert(temp >= 0); + + return(temp); +} + + +/* + * cost_result-- + * Determines and returns the cost of writing a relation of 'tuples' + * tuples of 'width' bytes out to a result relation. + * + * Returns a flonum. + * + */ +Cost +cost_result(int tuples, int width) +{ + Cost temp =0; + temp = temp + page_size(tuples,width); + temp = temp + _CPU_PAGE_WEIGHT_ * tuples; + Assert(temp >= 0); + return(temp); +} + +/* + * cost_nestloop-- + * Determines and returns the cost of joining two relations using the + * nested loop algorithm. + * + * 'outercost' is the (disk+cpu) cost of scanning the outer relation + * 'innercost' is the (disk+cpu) cost of scanning the inner relation + * 'outertuples' is the number of tuples in the outer relation + * + * Returns a flonum. + * + */ +Cost +cost_nestloop(Cost outercost, + Cost innercost, + int outertuples, + int innertuples, + int outerpages, + bool is_indexjoin) +{ + Cost temp =0; + + if ( !_enable_nestloop_ ) + temp += _disable_cost_; + temp += outercost; + temp += outertuples * innercost; + Assert(temp >= 0); + + return(temp); +} + +/* + * cost_mergesort-- + * 'outercost' and 'innercost' are the (disk+cpu) costs of scanning the + * outer and inner relations + * 'outersortkeys' and 'innersortkeys' are lists of the keys to be used + * to sort the outer and inner relations + * 'outertuples' and 'innertuples' are the number of tuples in the outer + * and inner relations + * 'outerwidth' and 'innerwidth' are the (typical) widths (in bytes) + * of the tuples of the outer and inner relations + * + * Returns a flonum. + * + */ +Cost +cost_mergesort(Cost outercost, + Cost innercost, + List *outersortkeys, + List *innersortkeys, + int outersize, + int innersize, + int outerwidth, + int innerwidth) +{ + Cost temp = 0; + + if ( !_enable_mergesort_ ) + temp += _disable_cost_; + + temp += outercost; + temp += innercost; + temp += cost_sort(outersortkeys,outersize,outerwidth,false); + temp += cost_sort(innersortkeys,innersize,innerwidth,false); + temp += _CPU_PAGE_WEIGHT_ * (outersize + innersize); + Assert(temp >= 0); + + return(temp); +} + +/* + * cost_hashjoin-- XXX HASH + * 'outercost' and 'innercost' are the (disk+cpu) costs of scanning the + * outer and inner relations + * 'outerkeys' and 'innerkeys' are lists of the keys to be used + * to hash the outer and inner relations + * 'outersize' and 'innersize' are the number of tuples in the outer + * and inner relations + * 'outerwidth' and 'innerwidth' are the (typical) widths (in bytes) + * of the tuples of the outer and inner relations + * + * Returns a flonum. + */ +Cost +cost_hashjoin(Cost outercost, + Cost innercost, + List *outerkeys, + List *innerkeys, + int outersize, + int innersize, + int outerwidth, + int innerwidth) +{ + Cost temp = 0; + int outerpages = page_size (outersize,outerwidth); + int innerpages = page_size (innersize,innerwidth); + int nrun = ceil((double)outerpages/(double)NBuffers); + + if (outerpages < innerpages) + return _disable_cost_; + if ( !_enable_hashjoin_ ) + temp += _disable_cost_; +/* temp += outercost + (nrun + 1) * innercost; */ + /* + the innercost shouldn't be used it. Instead the + cost of hashing the innerpath should be used + + ASSUME innercost is 1 for now -- a horrible hack + - jolly + */ + temp += outercost + (nrun + 1); + + temp += _CPU_PAGE_WEIGHT_ * (outersize + nrun * innersize); + Assert(temp >= 0); + + return(temp); +} + +/* + * compute-rel-size-- + * Computes the size of each relation in 'rel-list' (after applying + * restrictions), by multiplying the selectivity of each restriction + * by the original size of the relation. + * + * Sets the 'size' field for each relation entry with this computed size. + * + * Returns the size. + */ +int compute_rel_size(Rel *rel) +{ + Cost temp; + int temp1; + + temp = rel->tuples * product_selec(rel->clauseinfo); + Assert(temp >= 0); + if (temp >= (MAXINT - 1)) { + temp1 = MAXINT; + } else { + temp1 = ceil((double) temp); + } + Assert(temp1 >= 0); + Assert(temp1 <= MAXINT); + return(temp1); +} + +/* + * compute-rel-width-- + * Computes the width in bytes of a tuple from 'rel'. + * + * Returns the width of the tuple as a fixnum. + */ +int +compute_rel_width(Rel *rel) +{ + return (compute_targetlist_width(get_actual_tlist(rel->targetlist))); +} + +/* + * compute-targetlist-width-- + * Computes the width in bytes of a tuple made from 'targetlist'. + * + * Returns the width of the tuple as a fixnum. + */ +int +compute_targetlist_width(List *targetlist) +{ + List *temp_tl; + int tuple_width = 0; + + foreach (temp_tl, targetlist) { + tuple_width = tuple_width + + compute_attribute_width(lfirst(temp_tl)); + } + return(tuple_width); +} + +/* + * compute-attribute-width-- + * Given a target list entry, find the size in bytes of the attribute. + * + * If a field is variable-length, it is assumed to be at least the size + * of a TID field. + * + * Returns the width of the attribute as a fixnum. + */ +static int +compute_attribute_width(TargetEntry *tlistentry) +{ + int width = get_typlen(tlistentry->resdom->restype); + if (width < 0) + return(_DEFAULT_ATTRIBUTE_WIDTH_); + else + return(width); +} + +/* + * compute-joinrel-size-- + * Computes the size of the join relation 'joinrel'. + * + * Returns a fixnum. + */ +int +compute_joinrel_size(JoinPath *joinpath) +{ + Cost temp = 1.0; + int temp1 = 0; + + temp *= ((Path*)joinpath->outerjoinpath)->parent->size; + temp *= ((Path*)joinpath->innerjoinpath)->parent->size; + + temp = temp * product_selec(joinpath->pathclauseinfo); + if (temp >= (MAXINT -1)) { + temp1 = MAXINT; + } else { + /* should be ceil here, we don't want joinrel size's of one, do we? */ + temp1 = ceil((double)temp); + } + Assert(temp1 >= 0); + + return(temp1); +} + +/* + * page-size-- + * Returns an estimate of the number of pages covered by a given + * number of tuples of a given width (size in bytes). + */ +int page_size(int tuples, int width) +{ + int temp =0; + + temp = ceil((double)(tuples * (width + sizeof(HeapTupleData))) + / BLCKSZ); + Assert(temp >= 0); + return(temp); +} + +static double +base_log(double x, double b) +{ + return(log(x)/log(b)); +} diff --git a/src/backend/optimizer/path/hashutils.c b/src/backend/optimizer/path/hashutils.c new file mode 100644 index 00000000000..cdbd9b6d901 --- /dev/null +++ b/src/backend/optimizer/path/hashutils.c @@ -0,0 +1,120 @@ +/*------------------------------------------------------------------------- + * + * hashutils.c-- + * Utilities for finding applicable merge clauses and pathkeys + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/hashutils.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "nodes/pg_list.h" +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/clauses.h" + + +static HInfo *match_hashop_hashinfo(Oid hashop, List *hashinfo_list); + +/* + * group-clauses-by-hashop-- + * If a join clause node in 'clauseinfo-list' is hashjoinable, store + * it within a hashinfo node containing other clause nodes with the same + * hash operator. + * + * 'clauseinfo-list' is the list of clauseinfo nodes + * 'inner-relid' is the relid of the inner join relation + * + * Returns the new list of hashinfo nodes. + * + */ +List * +group_clauses_by_hashop(List *clauseinfo_list, + int inner_relid) +{ + List *hashinfo_list = NIL; + CInfo *clauseinfo = (CInfo*)NULL; + List *i = NIL; + Oid hashjoinop = 0; + + foreach (i,clauseinfo_list) { + clauseinfo = (CInfo*)lfirst(i); + hashjoinop = clauseinfo->hashjoinoperator; + + /* + * Create a new hashinfo node and add it to 'hashinfo-list' if one + * does not yet exist for this hash operator. + */ + if (hashjoinop ) { + HInfo *xhashinfo = (HInfo*)NULL; + Expr *clause = clauseinfo->clause; + Var *leftop = get_leftop(clause); + Var *rightop = get_rightop(clause); + JoinKey *keys = (JoinKey*)NULL; + + xhashinfo = + match_hashop_hashinfo(hashjoinop,hashinfo_list); + + if (inner_relid == leftop->varno){ + keys = makeNode(JoinKey); + keys->outer = rightop; + keys->inner = leftop; + } else { + keys = makeNode(JoinKey); + keys->outer = leftop; + keys->inner = rightop; + } + + if (xhashinfo==NULL) { + xhashinfo = makeNode(HInfo); + xhashinfo->hashop = hashjoinop; + + xhashinfo->jmethod.jmkeys = NIL; + xhashinfo->jmethod.clauses = NIL; + + /* XXX was push */ + hashinfo_list = lappend(hashinfo_list,xhashinfo); + hashinfo_list = nreverse(hashinfo_list); + } + + xhashinfo->jmethod.clauses = + lcons(clause, xhashinfo->jmethod.clauses); + + xhashinfo->jmethod.jmkeys = + lcons(keys, xhashinfo->jmethod.jmkeys); + } + } + return(hashinfo_list); +} + + +/* + * match-hashop-hashinfo-- + * Searches the list 'hashinfo-list' for a hashinfo node whose hash op + * field equals 'hashop'. + * + * Returns the node if it exists. + * + */ +static HInfo * +match_hashop_hashinfo(Oid hashop, List *hashinfo_list) +{ + Oid key = 0; + HInfo *xhashinfo = (HInfo*)NULL; + List *i = NIL; + + foreach( i, hashinfo_list) { + xhashinfo = (HInfo*)lfirst(i); + key = xhashinfo->hashop; + if (hashop == key) { /* found */ + return(xhashinfo); /* should be a hashinfo node ! */ + } + } + return((HInfo*)NIL); +} diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c new file mode 100644 index 00000000000..844571847f9 --- /dev/null +++ b/src/backend/optimizer/path/indxpath.c @@ -0,0 +1,1206 @@ +/*------------------------------------------------------------------------- + * + * indxpath.c-- + * Routines to determine which indices are usable for scanning a + * given relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> +#include "postgres.h" +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/nbtree.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" + +#include "utils/lsyscache.h" +#include "utils/elog.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/plancat.h" +#include "optimizer/keys.h" +#include "optimizer/cost.h" +#include "optimizer/pathnode.h" +#include "optimizer/xfunc.h" +#include "optimizer/ordering.h" + + +#include "catalog/catname.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_proc.h" + +#include "executor/executor.h" +#include "parser/parsetree.h" /* for getrelid() */ + + +static void match_index_orclauses(Rel *rel, Rel *index, int indexkey, + int xclass, List *clauseinfo_list); +static bool match_index_to_operand(int indexkey, Expr *operand, + Rel *rel, Rel *index); +static List *match_index_orclause(Rel *rel, Rel *index, int indexkey, + int xclass, List *or_clauses, List *other_matching_indices); +static List *group_clauses_by_indexkey(Rel *rel, Rel *index, + int *indexkeys, Oid *classes, List *clauseinfo_list, + bool join); +static CInfo *match_clause_to_indexkey(Rel *rel, Rel *index, int indexkey, + int xclass, CInfo *clauseInfo, bool join); +static bool pred_test(List *predicate_list, List *clauseinfo_list, + List *joininfo_list); +static bool one_pred_test(Expr *predicate, List *clauseinfo_list); +static bool one_pred_clause_expr_test(Expr *predicate, Node *clause); +static bool one_pred_clause_test(Expr *predicate, Node *clause); +static bool clause_pred_clause_test(Expr *predicate, Node *clause); +static List *indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list); +static List *index_innerjoin(Query *root, Rel *rel, + List *clausegroup_list, Rel *index); +static List *create_index_paths(Query *root, Rel *rel, Rel *index, + List *clausegroup_list, bool join); +static List *add_index_paths(List *indexpaths, List *new_indexpaths); +static bool function_index_operand(Expr *funcOpnd, Rel *rel, Rel *index); +static bool SingleAttributeIndex(Rel *index); + +/* If Spyros can use a constant PRS2_BOOL_TYPEID, I can use this */ +#define BOOL_TYPEID ((Oid) 16) + +/* + * find-index-paths-- + * Finds all possible index paths by determining which indices in the + * list 'indices' are usable. + * + * To be usable, an index must match against either a set of + * restriction clauses or join clauses. + * + * Note that the current implementation requires that there exist + * matching clauses for every key in the index (i.e., no partial + * matches are allowed). + * + * If an index can't be used with restriction clauses, but its keys + * match those of the result sort order (according to information stored + * within 'sortkeys'), then the index is also considered. + * + * 'rel' is the relation entry to which these index paths correspond + * 'indices' is a list of possible index paths + * 'clauseinfo-list' is a list of restriction clauseinfo nodes for 'rel' + * 'joininfo-list' is a list of joininfo nodes for 'rel' + * 'sortkeys' is a node describing the result sort order (from + * (find_sortkeys)) + * + * Returns a list of index nodes. + * + */ +List * +find_index_paths (Query *root, + Rel *rel, + List *indices, + List *clauseinfo_list, + List *joininfo_list) +{ + List *scanclausegroups = NIL; + List *scanpaths = NIL; + Rel *index = (Rel *)NULL; + List *joinclausegroups = NIL; + List *joinpaths = NIL; + List *retval = NIL; + extern List *add_index_paths(); + + if(indices == NIL) + return(NULL); + + index = (Rel*)lfirst (indices); + + retval = find_index_paths(root, + rel, + lnext (indices), + clauseinfo_list, + joininfo_list); + + /* If this is a partial index, return if it fails the predicate test */ + if (index->indpred != NIL) + if (!pred_test(index->indpred, clauseinfo_list, joininfo_list)) + return retval; + + /* 1. If this index has only one key, try matching it against + * subclauses of an 'or' clause. The fields of the clauseinfo + * nodes are marked with lists of the matching indices no path + * are actually created. + * + * XXX NOTE: Currently btrees dos not support indices with + * > 1 key, so the following test will always be true for + * now but we have decided not to support index-scans + * on disjunction . -- lp + */ + if (SingleAttributeIndex(index)) + { + match_index_orclauses (rel, + index, + index->indexkeys[0], + index->classlist[0], + clauseinfo_list); + } + + /* + * 2. If the keys of this index match any of the available + * restriction clauses, then create pathnodes corresponding + * to each group of usable clauses. + */ + scanclausegroups = group_clauses_by_indexkey(rel, + index, + index->indexkeys, + index->classlist, + clauseinfo_list, + false); + + scanpaths = NIL; + if (scanclausegroups != NIL) + scanpaths = create_index_paths (root, + rel, + index, + scanclausegroups, + false); + + /* + * 3. If this index can be used with any join clause, then + * create pathnodes for each group of usable clauses. An + * index can be used with a join clause if its ordering is + * useful for a mergejoin, or if the index can possibly be + * used for scanning the inner relation of a nestloop join. + */ + joinclausegroups = indexable_joinclauses(rel,index,joininfo_list); + joinpaths = NIL; + + if (joinclausegroups != NIL) + { + List *new_join_paths = create_index_paths(root, rel, + index, + joinclausegroups, + true); + List *innerjoin_paths = index_innerjoin(root, rel,joinclausegroups,index); + + rel->innerjoin = nconc (rel->innerjoin, innerjoin_paths); + joinpaths = new_join_paths; + } + + /* + * Some sanity checks to make sure that + * the indexpath is valid. + */ + if (joinpaths!=NULL) + retval = add_index_paths(joinpaths,retval); + if (scanpaths!=NULL) + retval = add_index_paths(scanpaths,retval); + + return retval; + +} + + +/**************************************************************************** + * ---- ROUTINES TO MATCH 'OR' CLAUSES ---- + ****************************************************************************/ + + +/* + * match-index-orclauses-- + * Attempt to match an index against subclauses within 'or' clauses. + * If the index does match, then the clause is marked with information + * about the index. + * + * Essentially, this adds 'index' to the list of indices in the + * ClauseInfo field of each of the clauses which it matches. + * + * 'rel' is the node of the relation on which the index is defined. + * 'index' is the index node. + * 'indexkey' is the (single) key of the index + * 'class' is the class of the operator corresponding to 'indexkey'. + * 'clauseinfo-list' is the list of available restriction clauses. + * + * Returns nothing. + * + */ +static void +match_index_orclauses(Rel *rel, + Rel *index, + int indexkey, + int xclass, + List *clauseinfo_list) +{ + CInfo *clauseinfo = (CInfo*)NULL; + List *i = NIL; + + foreach (i, clauseinfo_list) { + clauseinfo = (CInfo*)lfirst(i); + if (valid_or_clause(clauseinfo)) { + + /* Mark the 'or' clause with a list of indices which + * match each of its subclauses. The list is + * generated by adding 'index' to the existing + * list where appropriate. + */ + clauseinfo->indexids = + match_index_orclause (rel,index,indexkey, + xclass, + clauseinfo->clause->args, + clauseinfo->indexids); + } + } +} + +/* + * match_index_operand-- + * Generalize test for a match between an existing index's key + * and the operand on the rhs of a restriction clause. Now check + * for functional indices as well. + */ +static bool +match_index_to_operand(int indexkey, + Expr *operand, + Rel *rel, + Rel *index) +{ + /* + * Normal index. + */ + if (index->indproc == InvalidOid) + return match_indexkey_operand(indexkey, (Var*)operand, rel); + + /* + * functional index check + */ + return (function_index_operand(operand, rel, index)); +} + +/* + * match-index-orclause-- + * Attempts to match an index against the subclauses of an 'or' clause. + * + * A match means that: + * (1) the operator within the subclause can be used with one + * of the index's operator classes, and + * (2) there is a usable key that matches the variable within a + * sargable clause. + * + * 'or-clauses' are the remaining subclauses within the 'or' clause + * 'other-matching-indices' is the list of information on other indices + * that have already been matched to subclauses within this + * particular 'or' clause (i.e., a list previously generated by + * this routine) + * + * Returns a list of the form ((a b c) (d e f) nil (g h) ...) where + * a,b,c are nodes of indices that match the first subclause in + * 'or-clauses', d,e,f match the second subclause, no indices + * match the third, g,h match the fourth, etc. + */ +static List * +match_index_orclause(Rel *rel, + Rel *index, + int indexkey, + int xclass, + List *or_clauses, + List *other_matching_indices) +{ + Node *clause = NULL; + List *matched_indices = other_matching_indices; + List *index_list = NIL; + List *clist; + List *ind; + + if (!matched_indices) + matched_indices = lcons(NIL, NIL); + + for (clist = or_clauses, ind = matched_indices; + clist; + clist = lnext(clist), ind = lnext(ind)) + { + clause = lfirst(clist); + if (is_opclause (clause) && + op_class(((Oper*)((Expr*)clause)->oper)->opno, + xclass, index->relam) && + match_index_to_operand(indexkey, + (Expr*)get_leftop((Expr*)clause), + rel, + index) && + IsA(get_rightop((Expr*)clause),Const)) { + + matched_indices = lcons(index, matched_indices); + index_list = lappend(index_list, + matched_indices); + } + } + return(index_list); + +} + +/**************************************************************************** + * ---- ROUTINES TO CHECK RESTRICTIONS ---- + ****************************************************************************/ + + +/* + * DoneMatchingIndexKeys() - MACRO + * + * Determine whether we should continue matching index keys in a clause. + * Depends on if there are more to match or if this is a functional index. + * In the latter case we stop after the first match since the there can + * be only key (i.e. the function's return value) and the attributes in + * keys list represent the arguments to the function. -mer 3 Oct. 1991 + */ +#define DoneMatchingIndexKeys(indexkeys, index) \ + (indexkeys[0] == 0 || \ + (index->indproc != InvalidOid)) + +/* + * group-clauses-by-indexkey-- + * Determines whether there are clauses which will match each and every + * one of the remaining keys of an index. + * + * 'rel' is the node of the relation corresponding to the index. + * 'indexkeys' are the remaining index keys to be matched. + * 'classes' are the classes of the index operators on those keys. + * 'clauses' is either: + * (1) the list of available restriction clauses on a single + * relation, or + * (2) a list of join clauses between 'rel' and a fixed set of + * relations, + * depending on the value of 'join'. + * 'startlist' is a list of those clause nodes that have matched the keys + * that have already been checked. + * 'join' is a flag indicating that the clauses being checked are join + * clauses. + * + * Returns all possible groups of clauses that will match (given that + * one or more clauses can match any of the remaining keys). + * E.g., if you have clauses A, B, and C, ((A B) (A C)) might be + * returned for an index with 2 keys. + * + */ +static List * +group_clauses_by_indexkey(Rel *rel, + Rel *index, + int *indexkeys, + Oid *classes, + List *clauseinfo_list, + bool join) +{ + List *curCinfo = NIL; + CInfo *matched_clause = (CInfo*)NULL; + List *clausegroup = NIL; + + + if (clauseinfo_list == NIL) + return NIL; + + foreach (curCinfo,clauseinfo_list) { + CInfo *temp = (CInfo*)lfirst(curCinfo); + int *curIndxKey = indexkeys; + Oid *curClass = classes; + + do { + /* + * If we can't find any matching clauses for the first of + * the remaining keys, give up. + */ + matched_clause = match_clause_to_indexkey (rel, + index, + curIndxKey[0], + curClass[0], + temp, + join); + if (!matched_clause) + break; + + clausegroup = lcons(matched_clause, clausegroup); + curIndxKey++; + curClass++; + + } while ( !DoneMatchingIndexKeys(curIndxKey, index) ); + } + + if (clausegroup != NIL) + return(lcons(clausegroup, NIL)); + return NIL; +} + +/* + * IndexScanableClause () MACRO + * + * Generalize condition on which we match a clause with an index. + * Now we can match with functional indices. + */ +#define IndexScanableOperand(opnd, indkeys, rel, index) \ + ((index->indproc == InvalidOid) ? \ + equal_indexkey_var(indkeys,opnd) : \ + function_index_operand((Expr*)opnd,rel,index)) + +/* + * match_clause_to-indexkey-- + * Finds the first of a relation's available restriction clauses that + * matches a key of an index. + * + * To match, the clause must: + * (1) be in the form (op var const) if the clause is a single- + * relation clause, and + * (2) contain an operator which is in the same class as the index + * operator for this key. + * + * If the clause being matched is a join clause, then 'join' is t. + * + * Returns a single clauseinfo node corresponding to the matching + * clause. + * + * NOTE: returns nil if clause is an or_clause. + * + */ +static CInfo * +match_clause_to_indexkey(Rel *rel, + Rel *index, + int indexkey, + int xclass, + CInfo *clauseInfo, + bool join) +{ + Expr *clause = clauseInfo->clause; + Var *leftop, *rightop; + Oid join_op = InvalidOid; + bool isIndexable = false; + + if (or_clause((Node*)clause) || + not_clause((Node*)clause) || single_node((Node*)clause)) + return ((CInfo*)NULL); + + leftop = get_leftop(clause); + rightop = get_rightop(clause); + /* + * If this is not a join clause, check for clauses of the form: + * (operator var/func constant) and (operator constant var/func) + */ + if (!join) + { + Oid restrict_op = InvalidOid; + + /* + * Check for standard s-argable clause + */ + if (IsA(rightop,Const)) + { + restrict_op = ((Oper*)((Expr*)clause)->oper)->opno; + isIndexable = + ( op_class(restrict_op, xclass, index->relam) && + IndexScanableOperand(leftop, + indexkey, + rel, + index) ); + } + + /* + * Must try to commute the clause to standard s-arg format. + */ + else if (IsA(leftop,Const)) + { + restrict_op = + get_commutator(((Oper*)((Expr*)clause)->oper)->opno); + + if ( (restrict_op != InvalidOid) && + op_class(restrict_op, xclass, index->relam) && + IndexScanableOperand(rightop, + indexkey,rel,index) ) + { + isIndexable = true; + /* + * In place list modification. + * (op const var/func) -> (op var/func const) + */ + /* BUG! Old version: + CommuteClause(clause, restrict_op); + */ + CommuteClause((Node*)clause); + } + } + } + /* + * Check for an indexable scan on one of the join relations. + * clause is of the form (operator var/func var/func) + */ + else + { + if (match_index_to_operand(indexkey,(Expr*)rightop,rel,index)) { + + join_op = get_commutator(((Oper*)((Expr*)clause)->oper)->opno); + + } else if (match_index_to_operand(indexkey, + (Expr*)leftop,rel,index)) { + join_op = ((Oper*)((Expr*)clause)->oper)->opno; + } + + if ( join_op && op_class(join_op,xclass,index->relam) && + join_clause_p((Node*)clause)) + { + isIndexable = true; + + /* + * If we're using the operand's commutator we must + * commute the clause. + */ + if (join_op != ((Oper*)((Expr*)clause)->oper)->opno) + CommuteClause((Node*)clause); + } + } + + if (isIndexable) + return(clauseInfo); + + return(NULL); +} + +/**************************************************************************** + * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- + ****************************************************************************/ + +/* + * pred_test-- + * Does the "predicate inclusion test" for partial indexes. + * + * Recursively checks whether the clauses in clauseinfo_list imply + * that the given predicate is true. + * + * This routine (together with the routines it calls) iterates over + * ANDs in the predicate first, then reduces the qualification + * clauses down to their constituent terms, and iterates over ORs + * in the predicate last. This order is important to make the test + * succeed whenever possible (assuming the predicate has been + * successfully cnfify()-ed). --Nels, Jan '93 + */ +static bool +pred_test(List *predicate_list, List *clauseinfo_list, List *joininfo_list) +{ + List *pred, *items, *item; + + /* + * Note: if Postgres tried to optimize queries by forming equivalence + * classes over equi-joined attributes (i.e., if it recognized that a + * qualification such as "where a.b=c.d and a.b=5" could make use of + * an index on c.d), then we could use that equivalence class info + * here with joininfo_list to do more complete tests for the usability + * of a partial index. For now, the test only uses restriction + * clauses (those in clauseinfo_list). --Nels, Dec '92 + */ + + if (predicate_list == NULL) + return true; /* no predicate: the index is usable */ + if (clauseinfo_list == NULL) + return false; /* no restriction clauses: the test must fail */ + + foreach (pred, predicate_list) { + /* if any clause is not implied, the whole predicate is not implied */ + if (and_clause(lfirst(pred))) { + items = ((Expr*)lfirst(pred))->args; + foreach (item, items) { + if (!one_pred_test(lfirst(item), clauseinfo_list)) + return false; + } + } + else if (!one_pred_test(lfirst(pred), clauseinfo_list)) + return false; + } + return true; +} + + +/* + * one_pred_test-- + * Does the "predicate inclusion test" for one conjunct of a predicate + * expression. + */ +static bool +one_pred_test(Expr *predicate, List *clauseinfo_list) +{ + CInfo *clauseinfo; + List *item; + + Assert(predicate != NULL); + foreach (item, clauseinfo_list) { + clauseinfo = (CInfo *)lfirst(item); + /* if any clause implies the predicate, return true */ + if (one_pred_clause_expr_test(predicate, (Node*)clauseinfo->clause)) + return true; + } + return false; +} + + +/* + * one_pred_clause_expr_test-- + * Does the "predicate inclusion test" for a general restriction-clause + * expression. + */ +static bool +one_pred_clause_expr_test(Expr *predicate, Node *clause) +{ + List *items, *item; + + if (is_opclause(clause)) + return one_pred_clause_test(predicate, clause); + else if (or_clause(clause)) { + items = ((Expr*)clause)->args; + foreach (item, items) { + /* if any OR item doesn't imply the predicate, clause doesn't */ + if (!one_pred_clause_expr_test(predicate, lfirst(item))) + return false; + } + return true; + }else if (and_clause(clause)) { + items = ((Expr*)clause)->args; + foreach (item, items) { + /* if any AND item implies the predicate, the whole clause does */ + if (one_pred_clause_expr_test(predicate, lfirst(item))) + return true; + } + return false; + }else { + /* unknown clause type never implies the predicate */ + return false; + } +} + + +/* + * one_pred_clause_test-- + * Does the "predicate inclusion test" for one conjunct of a predicate + * expression for a simple restriction clause. + */ +static bool +one_pred_clause_test(Expr *predicate, Node *clause) +{ + List *items, *item; + + if (is_opclause((Node*)predicate)) + return clause_pred_clause_test(predicate, clause); + else if (or_clause((Node*)predicate)) { + items = predicate->args; + foreach (item, items) { + /* if any item is implied, the whole predicate is implied */ + if (one_pred_clause_test(lfirst(item), clause)) + return true; + } + return false; + }else if (and_clause((Node*)predicate)) { + items = predicate->args; + foreach (item, items) { + /* + * if any item is not implied, the whole predicate is not + * implied + */ + if (!one_pred_clause_test(lfirst(item), clause)) + return false; + } + return true; + } + else { + elog(DEBUG, "Unsupported predicate type, index will not be used"); + return false; + } +} + + +/* + * Define an "operator implication table" for btree operators ("strategies"). + * The "strategy numbers" are: (1) < (2) <= (3) = (4) >= (5) > + * + * The interpretation of: + * + * test_op = BT_implic_table[given_op-1][target_op-1] + * + * where test_op, given_op and target_op are strategy numbers (from 1 to 5) + * of btree operators, is as follows: + * + * If you know, for some ATTR, that "ATTR given_op CONST1" is true, and you + * want to determine whether "ATTR target_op CONST2" must also be true, then + * you can use "CONST1 test_op CONST2" as a test. If this test returns true, + * then the target expression must be true; if the test returns false, then + * the target expression may be false. + * + * An entry where test_op==0 means the implication cannot be determined, i.e., + * this test should always be considered false. + */ + +StrategyNumber BT_implic_table[BTMaxStrategyNumber][BTMaxStrategyNumber] = { + {2, 2, 0, 0, 0}, + {1, 2, 0, 0, 0}, + {1, 2, 3, 4, 5}, + {0, 0, 0, 4, 5}, + {0, 0, 0, 4, 4} +}; + + +/* + * clause_pred_clause_test-- + * Use operator class info to check whether clause implies predicate. + * + * Does the "predicate inclusion test" for a "simple clause" predicate + * for a single "simple clause" restriction. Currently, this only handles + * (binary boolean) operators that are in some btree operator class. + * Eventually, rtree operators could also be handled by defining an + * appropriate "RT_implic_table" array. + */ +static bool +clause_pred_clause_test(Expr *predicate, Node *clause) +{ + Var *pred_var, *clause_var; + Const *pred_const, *clause_const; + Oid pred_op, clause_op, test_op; + Oid opclass_id; + StrategyNumber pred_strategy, clause_strategy, test_strategy; + Oper *test_oper; + Expr *test_expr; + bool test_result, isNull; + Relation relation; + HeapScanDesc scan; + HeapTuple tuple; + ScanKeyData entry[3]; + Form_pg_amop form; + + pred_var = (Var*)get_leftop(predicate); + pred_const = (Const*)get_rightop(predicate); + clause_var = (Var*)get_leftop((Expr*)clause); + clause_const = (Const*)get_rightop((Expr*)clause); + + /* Check the basic form; for now, only allow the simplest case */ + if (!is_opclause(clause) || + !IsA(clause_var,Var) || + !IsA(clause_const,Const) || + !IsA(predicate->oper,Oper) || + !IsA(pred_var,Var) || + !IsA(pred_const,Const)) { + return false; + } + + /* + * The implication can't be determined unless the predicate and the clause + * refer to the same attribute. + */ + if (clause_var->varattno != pred_var->varattno) + return false; + + /* Get the operators for the two clauses we're comparing */ + pred_op = ((Oper*)((Expr*)predicate)->oper)->opno; + clause_op = ((Oper*)((Expr*)clause)->oper)->opno; + + + /* + * 1. Find a "btree" strategy number for the pred_op + */ + /* XXX - hardcoded amopid value 403 to find "btree" operator classes */ + ScanKeyEntryInitialize(&entry[0], 0, + Anum_pg_amop_amopid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(403)); + + ScanKeyEntryInitialize(&entry[1], 0, + Anum_pg_amop_amopopr, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(pred_op)); + + relation = heap_openr(AccessMethodOperatorRelationName); + + /* + * The following assumes that any given operator will only be in a single + * btree operator class. This is true at least for all the pre-defined + * operator classes. If it isn't true, then whichever operator class + * happens to be returned first for the given operator will be used to + * find the associated strategy numbers for the test. --Nels, Jan '93 + */ + scan = heap_beginscan(relation, false, NowTimeQual, 2, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown pred_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the predicate operator's strategy number (1 to 5) */ + pred_strategy = (StrategyNumber)form->amopstrategy; + + /* Remember which operator class this strategy number came from */ + opclass_id = form->amopclaid; + + heap_endscan(scan); + + + /* + * 2. From the same opclass, find a strategy num for the clause_op + */ + ScanKeyEntryInitialize(&entry[1], 0, + Anum_pg_amop_amopclaid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(opclass_id)); + + ScanKeyEntryInitialize(&entry[2], 0, + Anum_pg_amop_amopopr, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(clause_op)); + + scan = heap_beginscan(relation, false, NowTimeQual, 3, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown clause_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the restriction clause operator's strategy number (1 to 5) */ + clause_strategy = (StrategyNumber)form->amopstrategy; + heap_endscan(scan); + + + /* + * 3. Look up the "test" strategy number in the implication table + */ + + test_strategy = BT_implic_table[clause_strategy-1][pred_strategy-1]; + if (test_strategy == 0) + return false; /* the implication cannot be determined */ + + + /* + * 4. From the same opclass, find the operator for the test strategy + */ + + ScanKeyEntryInitialize(&entry[2], 0, + Anum_pg_amop_amopstrategy, + Integer16EqualRegProcedure, + Int16GetDatum(test_strategy)); + + scan = heap_beginscan(relation, false, NowTimeQual, 3, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown test_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the test operator */ + test_op = form->amopopr; + heap_endscan(scan); + + + /* + * 5. Evaluate the test + */ + test_oper = makeOper(test_op, /* opno */ + InvalidOid, /* opid */ + BOOL_TYPEID, /* opresulttype */ + 0, /* opsize */ + NULL); /* op_fcache */ + (void) replace_opid(test_oper); + + test_expr = make_opclause(test_oper, + copyObject(clause_const), + copyObject(pred_const)); + +#ifndef OMIT_PARTIAL_INDEX + test_result = ExecEvalExpr((Node*)test_expr, NULL, &isNull, NULL); +#endif /* OMIT_PARTIAL_INDEX */ + if (isNull) { + elog(DEBUG, "clause_pred_clause_test: null test result"); + return false; + } + return test_result; +} + + +/**************************************************************************** + * ---- ROUTINES TO CHECK JOIN CLAUSES ---- + ****************************************************************************/ + +/* + * indexable-joinclauses-- + * Finds all groups of join clauses from among 'joininfo-list' that can + * be used in conjunction with 'index'. + * + * The first clause in the group is marked as having the other relation + * in the join clause as its outer join relation. + * + * Returns a list of these clause groups. + * + */ +static List * +indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list) +{ + JInfo *joininfo = (JInfo*)NULL; + List *cg_list = NIL; + List *i = NIL; + List *clausegroups = NIL; + + foreach(i,joininfo_list) { + joininfo = (JInfo*)lfirst(i); + clausegroups = + group_clauses_by_indexkey (rel, + index, + index->indexkeys, + index->classlist, + joininfo->jinfoclauseinfo, + true); + + if (clausegroups != NIL) { + List *clauses = lfirst(clausegroups); + + ((CInfo*)lfirst(clauses))->cinfojoinid = + joininfo->otherrels; + } + cg_list = nconc(cg_list,clausegroups); + } + return(cg_list); +} + +/**************************************************************************** + * ---- PATH CREATION UTILITIES ---- + ****************************************************************************/ + +/* + * extract_restrict_clauses - + * the list of clause info contains join clauses and restriction clauses. + * This routine returns the restriction clauses only. + */ +static List * +extract_restrict_clauses(List *clausegroup) +{ + List *restrict_cls = NIL; + List *l; + + foreach (l, clausegroup) { + CInfo *cinfo = lfirst(l); + + if (!join_clause_p((Node*)cinfo->clause)) { + restrict_cls = lappend(restrict_cls, cinfo); + } + } + return restrict_cls; +} + +/* + * index-innerjoin-- + * Creates index path nodes corresponding to paths to be used as inner + * relations in nestloop joins. + * + * 'clausegroup-list' is a list of list of clauseinfo nodes which can use + * 'index' on their inner relation. + * + * Returns a list of index pathnodes. + * + */ +static List * +index_innerjoin(Query *root, Rel *rel, List *clausegroup_list, Rel *index) +{ + List *clausegroup = NIL; + List *cg_list = NIL; + List *i = NIL; + IndexPath *pathnode = (IndexPath*)NULL; + Cost temp_selec; + float temp_pages; + + foreach(i,clausegroup_list) { + List *attnos, *values, *flags; + + clausegroup = lfirst(i); + pathnode = makeNode(IndexPath); + + get_joinvars(lfirsti(rel->relids),clausegroup, + &attnos, &values, &flags); + index_selectivity(lfirsti(index->relids), + index->classlist, + get_opnos(clausegroup), + getrelid((int)lfirst(rel->relids), + root->rtable), + attnos, + values, + flags, + length(clausegroup), + &temp_pages, + &temp_selec); + pathnode->path.pathtype = T_IndexScan; + pathnode->path.parent = rel; + pathnode->indexid = index->relids; + pathnode->indexqual = clausegroup; + + pathnode->path.joinid = ((CInfo*)lfirst(clausegroup))->cinfojoinid; + + pathnode->path.path_cost = + cost_index((Oid)lfirst(index->relids), + (int)temp_pages, + temp_selec, + rel->pages, + rel->tuples, + index->pages, + index->tuples, + true); + + /* copy clauseinfo list into path for expensive function processing + -- JMH, 7/7/92 */ + pathnode->path.locclauseinfo = + set_difference(copyObject((Node*)rel->clauseinfo), + clausegroup); + +#if 0 /* fix xfunc */ + /* add in cost for expensive functions! -- JMH, 7/7/92 */ + if (XfuncMode != XFUNC_OFF) { + ((Path*)pathnode)->path_cost += + xfunc_get_path_cost((Path*)pathnode); + } +#endif + cg_list = lappend(cg_list,pathnode); + } + return(cg_list); +} + +/* + * create-index-paths-- + * Creates a list of index path nodes for each group of clauses + * (restriction or join) that can be used in conjunction with an index. + * + * 'rel' is the relation for which 'index' is defined + * 'clausegroup-list' is the list of clause groups (lists of clauseinfo + * nodes) grouped by mergesortorder + * 'join' is a flag indicating whether or not the clauses are join + * clauses + * + * Returns a list of new index path nodes. + * + */ +static List * +create_index_paths(Query *root, + Rel *rel, + Rel *index, + List *clausegroup_list, + bool join) +{ + List *clausegroup = NIL; + List *ip_list = NIL; + List *i = NIL; + List *j = NIL; + IndexPath *temp_path; + + foreach(i, clausegroup_list) { + CInfo *clauseinfo; + List *temp_node = NIL; + bool temp = true; + + clausegroup = lfirst(i); + + foreach (j,clausegroup) { + clauseinfo = (CInfo*)lfirst(j); + if (!(join_clause_p((Node*)clauseinfo->clause) && + equal_path_merge_ordering(index->ordering, + clauseinfo->mergesortorder))) { + temp = false; + } + } + + if (!join || temp) { /* restriction, ordering scan */ + temp_path = create_index_path (root, rel,index,clausegroup,join); + temp_node = + lcons(temp_path, NIL); + ip_list = nconc(ip_list,temp_node); + } + } + return(ip_list); +} + +static List * +add_index_paths(List *indexpaths, List *new_indexpaths) +{ + return append(indexpaths, new_indexpaths); +} + +static bool +function_index_operand(Expr *funcOpnd, Rel *rel, Rel *index) +{ + Oid heapRelid = (Oid)lfirst(rel->relids); + Func *function; + List *funcargs; + int *indexKeys = index->indexkeys; + List *arg; + int i; + + /* + * sanity check, make sure we know what we're dealing with here. + */ + if (funcOpnd==NULL || + nodeTag(funcOpnd)!=T_Expr || funcOpnd->opType!=FUNC_EXPR || + funcOpnd->oper==NULL || indexKeys==NULL) + return false; + + function = (Func*)funcOpnd->oper; + funcargs = funcOpnd->args; + + if (function->funcid != index->indproc) + return false; + + /* + * Check that the arguments correspond to the same arguments used + * to create the functional index. To do this we must check that + * 1. refer to the right relatiion. + * 2. the args have the right attr. numbers in the right order. + * + * + * Check all args refer to the correct relation (i.e. the one with + * the functional index defined on it (rel). To do this we can + * simply compare range table entry numbers, they must be the same. + */ + foreach (arg, funcargs) { + if (heapRelid != ((Var*)lfirst(arg))->varno) + return false; + } + + /* + * check attr numbers and order. + */ + i = 0; + foreach (arg, funcargs) { + + if (indexKeys[i]==0) + return (false); + + if (((Var*)lfirst(arg))->varattno != indexKeys[i]) + return (false); + + i++; + } + + return true; +} + +static bool +SingleAttributeIndex(Rel *index) +{ + /* + * return false for now as I don't know if we support index scans + * on disjunction and the code doesn't work + */ + return (false); + +#if 0 + /* + * Non-functional indices. + */ + if (index->indproc == InvalidOid) + return (index->indexkeys[0] != 0 && + index->indexkeys[1] == 0); + + /* + * We have a functional index which is a single attr index + */ + return true; +#endif +} diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c new file mode 100644 index 00000000000..e727388715c --- /dev/null +++ b/src/backend/optimizer/path/joinpath.c @@ -0,0 +1,623 @@ +/*------------------------------------------------------------------------- + * + * joinpath.c-- + * Routines to find all possible paths for processing a set of joins + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> + +#include "storage/buf_internals.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/plannodes.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/keys.h" +#include "optimizer/cost.h" /* for _enable_{hashjoin, _enable_mergesort} */ + +static Path *best_innerjoin(List *join_paths, List *outer_relid); +static List *sort_inner_and_outer(Rel *joinrel, Rel *outerrel, Rel *innerrel, + List *mergeinfo_list); +static List *match_unsorted_outer(Rel *joinrel, Rel *outerrel, Rel *innerrel, + List *outerpath_list, Path *cheapest_inner, Path *best_innerjoin, + List *mergeinfo_list); +static List *match_unsorted_inner(Rel *joinrel, Rel *outerrel, Rel *innerrel, + List *innerpath_list, List *mergeinfo_list); +static bool EnoughMemoryForHashjoin(Rel *hashrel); +static List *hash_inner_and_outer(Rel *joinrel, Rel *outerrel, Rel *innerrel, + List *hashinfo_list); + +/* + * find-all-join-paths-- + * Creates all possible ways to process joins for each of the join + * relations in the list 'joinrels.' Each unique path will be included + * in the join relation's 'pathlist' field. + * + * In postgres, n-way joins are handled left-only(permuting clauseless + * joins doesn't usually win much). + * + * if BushyPlanFlag is true, bushy tree plans will be generated + * + * 'joinrels' is the list of relation entries to be joined + * + * Modifies the pathlist field of the appropriate rel node to contain + * the unique join paths. + * If bushy trees are considered, may modify the relid field of the + * join rel nodes to flatten the lists. + * + * Returns nothing of interest. (?) + * It does a destructive modification. + */ +void +find_all_join_paths(Query *root, List *joinrels) +{ + List *mergeinfo_list = NIL; + List *hashinfo_list = NIL; + List *temp_list = NIL; + List *path = NIL; + + while (joinrels != NIL) { + Rel *joinrel = (Rel *)lfirst(joinrels); + List *innerrelids; + List *outerrelids; + Rel *innerrel; + Rel *outerrel; + Path *bestinnerjoin; + List *pathlist = NIL; + + innerrelids = lsecond(joinrel->relids); + outerrelids = lfirst(joinrel->relids); + + /* + * base relation id is an integer and join relation relid is a + * list of integers. + */ + innerrel = (length(innerrelids)==1)? + get_base_rel(root, lfirsti(innerrelids)) : get_join_rel(root,innerrelids); + outerrel = (length(outerrelids)==1)? + get_base_rel(root, lfirsti(outerrelids)) : get_join_rel(root, outerrelids); + + bestinnerjoin = best_innerjoin(innerrel->innerjoin, + outerrel->relids); + if( _enable_mergesort_ ) { + mergeinfo_list = + group_clauses_by_order(joinrel->clauseinfo, + lfirsti(innerrel->relids)); + } + + if( _enable_hashjoin_ ) { + hashinfo_list = + group_clauses_by_hashop(joinrel->clauseinfo, + lfirsti(innerrel->relids)); + } + + /* need to flatten the relids list */ + joinrel->relids = intAppend(outerrelids, innerrelids); + + /* + * 1. Consider mergesort paths where both relations must be + * explicitly sorted. + */ + pathlist = sort_inner_and_outer(joinrel,outerrel, + innerrel,mergeinfo_list); + + /* + * 2. Consider paths where the outer relation need not be explicitly + * sorted. This may include either nestloops and mergesorts where + * the outer path is already ordered. + */ + pathlist = + add_pathlist(joinrel, pathlist, + match_unsorted_outer(joinrel, + outerrel, + innerrel, + outerrel->pathlist, + (Path*)innerrel->cheapestpath, + bestinnerjoin, + mergeinfo_list)); + + /* + * 3. Consider paths where the inner relation need not be explicitly + * sorted. This may include nestloops and mergesorts the actual + * nestloop nodes were constructed in (match-unsorted-outer). + */ + pathlist = + add_pathlist(joinrel,pathlist, + match_unsorted_inner(joinrel,outerrel, + innerrel, + innerrel->pathlist, + mergeinfo_list)); + + /* + * 4. Consider paths where both outer and inner relations must be + * hashed before being joined. + */ + + pathlist = + add_pathlist(joinrel, pathlist, + hash_inner_and_outer(joinrel,outerrel, + innerrel,hashinfo_list)); + + joinrel->pathlist = pathlist; + + /* + * 'OuterJoinCost is only valid when calling (match-unsorted-inner) + * with the same arguments as the previous invokation of + * (match-unsorted-outer), so clear the field before going on. + */ + temp_list = innerrel->pathlist; + foreach(path, temp_list) { + + /* + * XXX + * + * This gross hack is to get around an apparent optimizer bug on + * Sparc (or maybe it is a bug of ours?) that causes really wierd + * behavior. + */ + if (IsA_JoinPath(path)) { + ((Path*)lfirst(path))->outerjoincost = (Cost) 0; + } + + /* do it iff it is a join path, which is not always + true, esp since the base level */ + } + + joinrels = lnext(joinrels); + } +} + +/* + * best-innerjoin-- + * Find the cheapest index path that has already been identified by + * (indexable_joinclauses) as being a possible inner path for the given + * outer relation in a nestloop join. + * + * 'join-paths' is a list of join nodes + * 'outer-relid' is the relid of the outer join relation + * + * Returns the pathnode of the selected path. + */ +static Path * +best_innerjoin(List *join_paths, List *outer_relids) +{ + Path *cheapest = (Path*)NULL; + List *join_path; + + foreach(join_path, join_paths) { + Path *path = (Path *)lfirst(join_path); + + if (intMember(lfirsti(path->joinid), outer_relids) + && ((cheapest==NULL || + path_is_cheaper((Path*)lfirst(join_path),cheapest)))) { + + cheapest = (Path*)lfirst(join_path); + } + } + return(cheapest); +} + +/* + * sort-inner-and-outer-- + * Create mergesort join paths by explicitly sorting both the outer and + * inner join relations on each available merge ordering. + * + * 'joinrel' is the join relation + * 'outerrel' is the outer join relation + * 'innerrel' is the inner join relation + * 'mergeinfo-list' is a list of nodes containing info on(mergesortable) + * clauses for joining the relations + * + * Returns a list of mergesort paths. + */ +static List * +sort_inner_and_outer(Rel *joinrel, + Rel *outerrel, + Rel *innerrel, + List *mergeinfo_list) +{ + List *ms_list = NIL; + MInfo *xmergeinfo = (MInfo*)NULL; + MergePath *temp_node = (MergePath*)NULL; + List *i; + List *outerkeys = NIL; + List *innerkeys = NIL; + List *merge_pathkeys = NIL; + + foreach(i, mergeinfo_list) { + xmergeinfo = (MInfo *)lfirst(i); + + outerkeys = + extract_path_keys(xmergeinfo->jmethod.jmkeys, + outerrel->targetlist, + OUTER); + + innerkeys = + extract_path_keys(xmergeinfo->jmethod.jmkeys, + innerrel->targetlist, + INNER); + + merge_pathkeys = + new_join_pathkeys(outerkeys, joinrel->targetlist, + xmergeinfo->jmethod.clauses); + + temp_node = + create_mergesort_path(joinrel, + outerrel->size, + innerrel->size, + outerrel->width, + innerrel->width, + (Path*)outerrel->cheapestpath, + (Path*)innerrel->cheapestpath, + merge_pathkeys, + xmergeinfo->m_ordering, + xmergeinfo->jmethod.clauses, + outerkeys, + innerkeys); + + ms_list = lappend(ms_list, temp_node); + } + return(ms_list); +} + +/* + * match-unsorted-outer-- + * Creates possible join paths for processing a single join relation + * 'joinrel' by employing either iterative substitution or + * mergesorting on each of its possible outer paths(assuming that the + * outer relation need not be explicitly sorted). + * + * 1. The inner path is the cheapest available inner path. + * 2. Mergesort wherever possible. Mergesorts are considered if there + * are mergesortable join clauses between the outer and inner join + * relations such that the outer path is keyed on the variables + * appearing in the clauses. The corresponding inner merge path is + * either a path whose keys match those of the outer path(if such a + * path is available) or an explicit sort on the appropriate inner + * join keys, whichever is cheaper. + * + * 'joinrel' is the join relation + * 'outerrel' is the outer join relation + * 'innerrel' is the inner join relation + * 'outerpath-list' is the list of possible outer paths + * 'cheapest-inner' is the cheapest inner path + * 'best-innerjoin' is the best inner index path(if any) + * 'mergeinfo-list' is a list of nodes containing info on mergesortable + * clauses + * + * Returns a list of possible join path nodes. + */ +static List * +match_unsorted_outer(Rel *joinrel, + Rel *outerrel, + Rel *innerrel, + List *outerpath_list, + Path *cheapest_inner, + Path *best_innerjoin, + List *mergeinfo_list) +{ + Path *outerpath = (Path*)NULL; + List *jp_list = NIL; + List *temp_node = NIL; + List *merge_pathkeys = NIL; + Path *nestinnerpath =(Path*)NULL; + List *paths = NIL; + List *i = NIL; + PathOrder *outerpath_ordering = NULL; + + foreach(i,outerpath_list) { + List *clauses = NIL; + List *matchedJoinKeys = NIL; + List *matchedJoinClauses = NIL; + MInfo *xmergeinfo = (MInfo*)NULL; + + outerpath = (Path*)lfirst(i); + + outerpath_ordering = &outerpath->p_ordering; + + if (outerpath_ordering) { + xmergeinfo = + match_order_mergeinfo(outerpath_ordering, + mergeinfo_list); + } + + if (xmergeinfo) { + clauses = xmergeinfo->jmethod.clauses; + } + + if (clauses) { + List *keys = xmergeinfo->jmethod.jmkeys; + List *clauses = xmergeinfo->jmethod.clauses; + + matchedJoinKeys = + match_pathkeys_joinkeys(outerpath->keys, + keys, + clauses, + OUTER, + &matchedJoinClauses); + merge_pathkeys = + new_join_pathkeys(outerpath->keys, + joinrel->targetlist, clauses); + } else { + merge_pathkeys = outerpath->keys; + } + + if(best_innerjoin && + path_is_cheaper(best_innerjoin, cheapest_inner)) { + nestinnerpath = best_innerjoin; + } else { + nestinnerpath = cheapest_inner; + } + + paths = lcons(create_nestloop_path(joinrel, + outerrel, + outerpath, + nestinnerpath, + merge_pathkeys), + NIL); + + if (clauses && matchedJoinKeys) { + bool path_is_cheaper_than_sort; + List *varkeys = NIL; + Path *mergeinnerpath = + match_paths_joinkeys(matchedJoinKeys, + outerpath_ordering, + innerrel->pathlist, + INNER); + + path_is_cheaper_than_sort = + (bool) (mergeinnerpath && + (mergeinnerpath->path_cost < + (cheapest_inner->path_cost + + cost_sort(matchedJoinKeys, + innerrel->size, + innerrel->width, + false)))); + if(!path_is_cheaper_than_sort) { + varkeys = + extract_path_keys(matchedJoinKeys, + innerrel->targetlist, + INNER); + } + + + /* + * Keep track of the cost of the outer path used with + * this ordered inner path for later processing in + * (match-unsorted-inner), since it isn't a sort and + * thus wouldn't otherwise be considered. + */ + if (path_is_cheaper_than_sort) { + mergeinnerpath->outerjoincost = outerpath->path_cost; + } else { + mergeinnerpath = cheapest_inner; + } + + temp_node = + lcons(create_mergesort_path(joinrel, + outerrel->size, + innerrel->size, + outerrel->width, + innerrel->width, + outerpath, + mergeinnerpath, + merge_pathkeys, + xmergeinfo->m_ordering, + matchedJoinClauses, + NIL, + varkeys), + paths); + } else { + temp_node = paths; + } + jp_list = nconc(jp_list, temp_node); + } + return(jp_list); +} + +/* + * match-unsorted-inner -- + * Find the cheapest ordered join path for a given(ordered, unsorted) + * inner join path. + * + * Scans through each path available on an inner join relation and tries + * matching its ordering keys against those of mergejoin clauses. + * If 1. an appropriately-ordered inner path and matching mergeclause are + * found, and + * 2. sorting the cheapest outer path is cheaper than using an ordered + * but unsorted outer path(as was considered in + * (match-unsorted-outer)), + * then this merge path is considered. + * + * 'joinrel' is the join result relation + * 'outerrel' is the outer join relation + * 'innerrel' is the inner join relation + * 'innerpath-list' is the list of possible inner join paths + * 'mergeinfo-list' is a list of nodes containing info on mergesortable + * clauses + * + * Returns a list of possible merge paths. + */ +static List * +match_unsorted_inner(Rel *joinrel, + Rel *outerrel, + Rel *innerrel, + List *innerpath_list, + List *mergeinfo_list) +{ + Path *innerpath = (Path*)NULL; + List *mp_list = NIL; + List *temp_node = NIL; + PathOrder *innerpath_ordering = NULL; + Cost temp1 = 0.0; + bool temp2 = false; + List *i = NIL; + + foreach (i, innerpath_list) { + MInfo *xmergeinfo = (MInfo*)NULL; + List *clauses = NIL; + List *matchedJoinKeys = NIL; + List *matchedJoinClauses = NIL; + + innerpath = (Path*)lfirst(i); + + innerpath_ordering = &innerpath->p_ordering; + + if (innerpath_ordering) { + xmergeinfo = + match_order_mergeinfo(innerpath_ordering, + mergeinfo_list); + } + + if (xmergeinfo) { + clauses = ((JoinMethod*)xmergeinfo)->clauses; + } + + if (clauses) { + List *keys = xmergeinfo->jmethod.jmkeys; + List *cls = xmergeinfo->jmethod.clauses; + + matchedJoinKeys = + match_pathkeys_joinkeys(innerpath->keys, + keys, + cls, + INNER, + &matchedJoinClauses); + } + + /* + * (match-unsorted-outer) if it is applicable. + * 'OuterJoinCost was set above in + */ + if (clauses && matchedJoinKeys) { + temp1 = outerrel->cheapestpath->path_cost + + cost_sort(matchedJoinKeys, outerrel->size, outerrel->width, + false); + + temp2 = (bool) (FLOAT_IS_ZERO(innerpath->outerjoincost) + || (innerpath->outerjoincost > temp1)); + + if(temp2) { + List *outerkeys = + extract_path_keys(matchedJoinKeys, + outerrel->targetlist, + OUTER); + List *merge_pathkeys = + new_join_pathkeys(outerkeys, + joinrel->targetlist, + clauses); + + temp_node = + lcons(create_mergesort_path(joinrel, + outerrel->size, + innerrel->size, + outerrel->width, + innerrel->width, + (Path*)outerrel->cheapestpath, + innerpath, + merge_pathkeys, + xmergeinfo->m_ordering, + matchedJoinClauses, + outerkeys, + NIL), + NIL); + + mp_list = nconc(mp_list,temp_node); + } + } + } + return(mp_list); + +} + +static bool +EnoughMemoryForHashjoin(Rel *hashrel) +{ + int ntuples; + int tupsize; + int pages; + + ntuples = hashrel->size; + if (ntuples == 0) ntuples = 1000; + tupsize = hashrel->width + sizeof(HeapTupleData); + pages = page_size(ntuples, tupsize); + /* + * if amount of buffer space below hashjoin threshold, + * return false + */ + if (ceil(sqrt((double)pages)) > NBuffers) + return false; + return true; +} + +/* + * hash-inner-and-outer-- XXX HASH + * Create hashjoin join paths by explicitly hashing both the outer and + * inner join relations on each available hash op. + * + * 'joinrel' is the join relation + * 'outerrel' is the outer join relation + * 'innerrel' is the inner join relation + * 'hashinfo-list' is a list of nodes containing info on(hashjoinable) + * clauses for joining the relations + * + * Returns a list of hashjoin paths. + */ +static List * +hash_inner_and_outer(Rel *joinrel, + Rel *outerrel, + Rel *innerrel, + List *hashinfo_list) +{ + HInfo *xhashinfo = (HInfo*)NULL; + List *hjoin_list = NIL; + HashPath *temp_node = (HashPath*)NULL; + List *i = NIL; + List *outerkeys = NIL; + List *innerkeys = NIL; + List *hash_pathkeys = NIL; + + foreach (i, hashinfo_list) { + xhashinfo = (HInfo*)lfirst(i); + outerkeys = + extract_path_keys(((JoinMethod*)xhashinfo)->jmkeys, + outerrel->targetlist, + OUTER); + innerkeys = + extract_path_keys(((JoinMethod*)xhashinfo)->jmkeys, + innerrel->targetlist, + INNER); + hash_pathkeys = + new_join_pathkeys(outerkeys, + joinrel->targetlist, + ((JoinMethod*)xhashinfo)->clauses); + + if (EnoughMemoryForHashjoin(innerrel)) { + temp_node = create_hashjoin_path(joinrel, + outerrel->size, + innerrel->size, + outerrel->width, + innerrel->width, + (Path*)outerrel->cheapestpath, + (Path*)innerrel->cheapestpath, + hash_pathkeys, + xhashinfo->hashop, + ((JoinMethod*)xhashinfo)->clauses, + outerkeys, + innerkeys); + hjoin_list = lappend(hjoin_list, temp_node); + } + } + return(hjoin_list); +} + diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c new file mode 100644 index 00000000000..b26e3364f93 --- /dev/null +++ b/src/backend/optimizer/path/joinrels.c @@ -0,0 +1,528 @@ +/*------------------------------------------------------------------------- + * + * joinrels.c-- + * Routines to determine which relations should be joined + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "optimizer/joininfo.h" +#include "optimizer/pathnode.h" + + +static List *find_clause_joins(Query *root, Rel *outer_rel, List *joininfo_list); +static List *find_clauseless_joins(Rel *outer_rel, List *inner_rels); +static Rel *init_join_rel(Rel *outer_rel, Rel *inner_rel, JInfo *joininfo); +static List *new_join_tlist(List *tlist, List *other_relids, + int first_resdomno); +static List *new_joininfo_list(List *joininfo_list, List *join_relids); +static void add_superrels(Rel *rel, Rel *super_rel); +static bool nonoverlap_rels(Rel *rel1, Rel *rel2); +static bool nonoverlap_sets(List *s1, List *s2); +static void set_joinrel_size(Rel *joinrel, Rel *outer_rel, Rel *inner_rel, + JInfo *jinfo); + +/* + * find-join-rels-- + * Find all possible joins for each of the outer join relations in + * 'outer-rels'. A rel node is created for each possible join relation, + * and the resulting list of nodes is returned. If at all possible, only + * those relations for which join clauses exist are considered. If none + * of these exist for a given relation, all remaining possibilities are + * considered. + * + * 'outer-rels' is the list of rel nodes + * + * Returns a list of rel nodes corresponding to the new join relations. + */ +List * +find_join_rels(Query *root, List *outer_rels) +{ + List *joins = NIL; + List *join_list = NIL; + List *r = NIL; + + foreach(r, outer_rels) { + Rel *outer_rel = (Rel *)lfirst(r); + + if(!(joins = find_clause_joins(root, outer_rel,outer_rel->joininfo))) + if (BushyPlanFlag) + joins = find_clauseless_joins(outer_rel,outer_rels); + else + joins = find_clauseless_joins(outer_rel,root->base_relation_list_); + + join_list = nconc(join_list, joins); + } + + return(join_list); +} + +/* + * find-clause-joins-- + * Determines whether joins can be performed between an outer relation + * 'outer-rel' and those relations within 'outer-rel's joininfo nodes + * (i.e., relations that participate in join clauses that 'outer-rel' + * participates in). This is possible if all but one of the relations + * contained within the join clauses of the joininfo node are already + * contained within 'outer-rel'. + * + * 'outer-rel' is the relation entry for the outer relation + * 'joininfo-list' is a list of join clauses which 'outer-rel' + * participates in + * + * Returns a list of new join relations. + */ +static List * +find_clause_joins(Query *root, Rel *outer_rel, List *joininfo_list) +{ + List *join_list = NIL; + List *i = NIL; + + foreach (i, joininfo_list) { + JInfo *joininfo = (JInfo*)lfirst(i); + Rel *rel; + + if(!joininfo->inactive) { + List *other_rels = joininfo->otherrels; + + if(other_rels != NIL) { + if(length(other_rels) == 1) { + rel = init_join_rel(outer_rel, + get_base_rel(root, lfirsti(other_rels)), + joininfo); + } else if (BushyPlanFlag) { + rel = init_join_rel(outer_rel, + get_join_rel(root, other_rels), + joininfo); + } else { + rel = NULL; + } + + if (rel != NULL) + join_list = lappend(join_list, rel); + } + } + } + + return(join_list); +} + +/* + * find-clauseless-joins-- + * Given an outer relation 'outer-rel' and a list of inner relations + * 'inner-rels', create a join relation between 'outer-rel' and each + * member of 'inner-rels' that isn't already included in 'outer-rel'. + * + * Returns a list of new join relations. + */ +static List * +find_clauseless_joins(Rel *outer_rel, List *inner_rels) +{ + Rel *inner_rel; + List *t_list = NIL; + List *temp_node = NIL; + List *i = NIL; + + foreach (i, inner_rels) { + inner_rel = (Rel *)lfirst(i); + if(nonoverlap_rels(inner_rel, outer_rel)) { + temp_node = lcons(init_join_rel(outer_rel, + inner_rel, + (JInfo*)NULL), + NIL); + t_list = nconc(t_list,temp_node); + } + } + + return(t_list); +} + +/* + * init-join-rel-- + * Creates and initializes a new join relation. + * + * 'outer-rel' and 'inner-rel' are relation nodes for the relations to be + * joined + * 'joininfo' is the joininfo node(join clause) containing both + * 'outer-rel' and 'inner-rel', if any exists + * + * Returns the new join relation node. + */ +static Rel * +init_join_rel(Rel *outer_rel, Rel *inner_rel, JInfo *joininfo) +{ + Rel *joinrel = makeNode(Rel); + List *joinrel_joininfo_list = NIL; + List *new_outer_tlist; + List *new_inner_tlist; + + /* + * Create a new tlist by removing irrelevant elements from both + * tlists of the outer and inner join relations and then merging + * the results together. + */ + new_outer_tlist = + new_join_tlist(outer_rel->targetlist, /* XXX 1-based attnos */ + inner_rel->relids, 1); + new_inner_tlist = + new_join_tlist(inner_rel->targetlist, /* XXX 1-based attnos */ + outer_rel->relids, + length(new_outer_tlist) + 1); + + joinrel->relids = NIL; + joinrel->indexed = false; + joinrel->pages = 0; + joinrel->tuples = 0; + joinrel->width = 0; +/* joinrel->targetlist = NIL;*/ + joinrel->pathlist = NIL; + joinrel->unorderedpath = (Path *)NULL; + joinrel->cheapestpath = (Path *)NULL; + joinrel->pruneable = true; + joinrel->classlist = NULL; + joinrel->relam = InvalidOid; + joinrel->ordering = NULL; + joinrel->clauseinfo = NIL; + joinrel->joininfo = NULL; + joinrel->innerjoin = NIL; + joinrel->superrels = NIL; + + joinrel->relids = lcons(outer_rel->relids, /* ??? aren't they lists? -ay */ + lcons(inner_rel->relids, NIL)); + + new_outer_tlist = nconc(new_outer_tlist,new_inner_tlist); + joinrel->targetlist = new_outer_tlist; + + if (joininfo) { + joinrel->clauseinfo = joininfo->jinfoclauseinfo; + if (BushyPlanFlag) + joininfo->inactive = true; + } + + joinrel_joininfo_list = + new_joininfo_list(append(outer_rel->joininfo, inner_rel->joininfo), + intAppend(outer_rel->relids, inner_rel->relids)); + + joinrel->joininfo = joinrel_joininfo_list; + + set_joinrel_size(joinrel, outer_rel, inner_rel, joininfo); + + return(joinrel); +} + +/* + * new-join-tlist-- + * Builds a join relations's target list by keeping those elements that + * will be in the final target list and any other elements that are still + * needed for future joins. For a target list entry to still be needed + * for future joins, its 'joinlist' field must not be empty after removal + * of all relids in 'other-relids'. + * + * 'tlist' is the target list of one of the join relations + * 'other-relids' is a list of relids contained within the other + * join relation + * 'first-resdomno' is the resdom number to use for the first created + * target list entry + * + * Returns the new target list. + */ +static List * +new_join_tlist(List *tlist, + List *other_relids, + int first_resdomno) +{ + int resdomno = first_resdomno - 1; + TargetEntry *xtl = NULL; + List *temp_node = NIL; + List *t_list = NIL; + List *i = NIL; + List *join_list = NIL; + bool in_final_tlist =false; + + + foreach(i,tlist) { + xtl= lfirst(i); + in_final_tlist = (join_list==NIL); + if( in_final_tlist) { + resdomno += 1; + temp_node = + lcons(create_tl_element(get_expr(xtl), + resdomno), + NIL); + t_list = nconc(t_list,temp_node); + } + } + + return(t_list); +} + +/* + * new-joininfo-list-- + * Builds a join relation's joininfo list by checking for join clauses + * which still need to used in future joins involving this relation. A + * join clause is still needed if there are still relations in the clause + * not contained in the list of relations comprising this join relation. + * New joininfo nodes are only created and added to + * 'current-joininfo-list' if a node for a particular join hasn't already + * been created. + * + * 'current-joininfo-list' contains a list of those joininfo nodes that + * have already been built + * 'joininfo-list' is the list of join clauses involving this relation + * 'join-relids' is a list of relids corresponding to the relations + * currently being joined + * + * Returns a list of joininfo nodes, new and old. + */ +static List * +new_joininfo_list(List *joininfo_list, List *join_relids) +{ + List *current_joininfo_list = NIL; + List *new_otherrels = NIL; + JInfo *other_joininfo = (JInfo*)NULL; + List *xjoininfo = NIL; + + foreach (xjoininfo, joininfo_list) { + JInfo *joininfo = (JInfo*)lfirst(xjoininfo); + + new_otherrels = joininfo->otherrels; + if (nonoverlap_sets(new_otherrels,join_relids)) { + other_joininfo = joininfo_member(new_otherrels, + current_joininfo_list); + if(other_joininfo) { + other_joininfo->jinfoclauseinfo = + (List*)LispUnion(joininfo->jinfoclauseinfo, + other_joininfo->jinfoclauseinfo); + }else { + other_joininfo = makeNode(JInfo); + + other_joininfo->otherrels = + joininfo->otherrels; + other_joininfo->jinfoclauseinfo = + joininfo->jinfoclauseinfo; + other_joininfo->mergesortable = + joininfo->mergesortable; + other_joininfo->hashjoinable = + joininfo->hashjoinable; + other_joininfo->inactive = false; + + current_joininfo_list = lcons(other_joininfo, + current_joininfo_list); + } + } + } + + return(current_joininfo_list); +} + +/* + * add-new-joininfos-- + * For each new join relation, create new joininfos that + * use the join relation as inner relation, and add + * the new joininfos to those rel nodes that still + * have joins with the join relation. + * + * 'joinrels' is a list of join relations. + * + * Modifies the joininfo field of appropriate rel nodes. + */ +void +add_new_joininfos(Query *root, List *joinrels, List *outerrels) +{ + List *xjoinrel = NIL; + List *xrelid = NIL; + List *xrel = NIL; + List *xjoininfo = NIL; + + foreach(xjoinrel, joinrels) { + Rel *joinrel = (Rel *)lfirst(xjoinrel); + foreach(xrelid, joinrel->relids) { + Relid relid = (Relid)lfirst(xrelid); + Rel *rel = get_join_rel(root, relid); + add_superrels(rel,joinrel); + } + } + foreach(xjoinrel, joinrels) { + Rel *joinrel = (Rel *)lfirst(xjoinrel); + + foreach(xjoininfo, joinrel->joininfo) { + JInfo *joininfo = (JInfo*)lfirst(xjoininfo); + List *other_rels = joininfo->otherrels; + List *clause_info = joininfo->jinfoclauseinfo; + bool mergesortable = joininfo->mergesortable; + bool hashjoinable = joininfo->hashjoinable; + + foreach(xrelid, other_rels) { + Relid relid = (Relid)lfirst(xrelid); + Rel *rel = get_join_rel(root, relid); + List *super_rels = rel->superrels; + List *xsuper_rel = NIL; + JInfo *new_joininfo = makeNode(JInfo); + + new_joininfo->otherrels = joinrel->relids; + new_joininfo->jinfoclauseinfo = clause_info; + new_joininfo->mergesortable = mergesortable; + new_joininfo->hashjoinable = hashjoinable; + new_joininfo->inactive = false; + rel->joininfo = + lappend(rel->joininfo, new_joininfo); + + foreach(xsuper_rel, super_rels) { + Rel *super_rel = (Rel *)lfirst(xsuper_rel); + + if( nonoverlap_rels(super_rel,joinrel) ) { + List *new_relids = super_rel->relids; + JInfo *other_joininfo = + joininfo_member(new_relids, + joinrel->joininfo); + + if (other_joininfo) { + other_joininfo->jinfoclauseinfo = + (List*)LispUnion(clause_info, + other_joininfo->jinfoclauseinfo); + } else { + JInfo *new_joininfo = makeNode(JInfo); + + new_joininfo->otherrels = new_relids; + new_joininfo->jinfoclauseinfo = clause_info; + new_joininfo->mergesortable = mergesortable; + new_joininfo->hashjoinable = hashjoinable; + new_joininfo->inactive = false; + joinrel->joininfo = + lappend(joinrel->joininfo, + new_joininfo); + } + } + } + } + } + } + foreach(xrel, outerrels) { + Rel *rel = (Rel *)lfirst(xrel); + rel->superrels = NIL; + } +} + +/* + * final-join-rels-- + * Find the join relation that includes all the original + * relations, i.e. the final join result. + * + * 'join-rel-list' is a list of join relations. + * + * Returns the list of final join relations. + */ +List * +final_join_rels(List *join_rel_list) +{ + List *xrel = NIL; + List *temp = NIL; + List *t_list = NIL; + + /* + * find the relations that has no further joins, + * i.e., its joininfos all have otherrels nil. + */ + foreach(xrel,join_rel_list) { + Rel *rel = (Rel *)lfirst(xrel); + List *xjoininfo = NIL; + bool final = true; + + foreach (xjoininfo, rel->joininfo) { + JInfo *joininfo = (JInfo*)lfirst(xjoininfo); + + if (joininfo->otherrels != NIL) { + final = false; + break; + } + } + if (final) { + temp = lcons(rel, NIL); + t_list = nconc(t_list, temp); + } + } + + return(t_list); +} + +/* + * add_superrels-- + * add rel to the temporary property list superrels. + * + * 'rel' a rel node + * 'super-rel' rel node of a join relation that includes rel + * + * Modifies the superrels field of rel + */ +static void +add_superrels(Rel *rel, Rel *super_rel) +{ + rel->superrels = lappend(rel->superrels, super_rel); +} + +/* + * nonoverlap-rels-- + * test if two join relations overlap, i.e., includes the same + * relation. + * + * 'rel1' and 'rel2' are two join relations + * + * Returns non-nil if rel1 and rel2 do not overlap. + */ +static bool +nonoverlap_rels(Rel *rel1, Rel *rel2) +{ + return(nonoverlap_sets(rel1->relids, rel2->relids)); +} + +static bool +nonoverlap_sets(List *s1, List *s2) +{ + List *x = NIL; + + foreach(x,s1) { + int e = lfirsti(x); + if(intMember(e,s2)) + return(false); + } + return(true); +} + +static void +set_joinrel_size(Rel *joinrel, Rel *outer_rel, Rel *inner_rel, JInfo *jinfo) +{ + int ntuples; + float selec; + + /* voodoo magic. but better than a size of 0. I have no idea why + we didn't set the size before. -ay 2/95 */ + if (jinfo==NULL) { + /* worst case: the cartesian product */ + ntuples = outer_rel->tuples * inner_rel->tuples; + } else { + selec = product_selec(jinfo->jinfoclauseinfo); +/* ntuples = Min(outer_rel->tuples,inner_rel->tuples) * selec; */ + ntuples = outer_rel->tuples * inner_rel->tuples * selec; + } + + /* I bet sizes less than 1 will screw up optimization so + make the best case 1 instead of 0 - jolly*/ + if (ntuples < 1) + ntuples = 1; + + joinrel->tuples = ntuples; +} diff --git a/src/backend/optimizer/path/joinutils.c b/src/backend/optimizer/path/joinutils.c new file mode 100644 index 00000000000..1be5a57f2ec --- /dev/null +++ b/src/backend/optimizer/path/joinutils.c @@ -0,0 +1,432 @@ +/*------------------------------------------------------------------------- + * + * joinutils.c-- + * Utilities for matching and building join and path keys + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/joinutils.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/plannodes.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/var.h" +#include "optimizer/keys.h" +#include "optimizer/tlist.h" +#include "optimizer/joininfo.h" +#include "optimizer/ordering.h" + + +static int match_pathkey_joinkeys(List *pathkey, List *joinkeys, + int which_subkey); +static bool every_func(List *joinkeys, List *pathkey, + int which_subkey); +static List *new_join_pathkey(List *subkeys, + List *considered_subkeys, List *join_rel_tlist, + List *joinclauses); +static List *new_matching_subkeys(Var *subkey, List *considered_subkeys, + List *join_rel_tlist, List *joinclauses); + +/**************************************************************************** + * KEY COMPARISONS + ****************************************************************************/ + +/* + * match-pathkeys-joinkeys-- + * Attempts to match the keys of a path against the keys of join clauses. + * This is done by looking for a matching join key in 'joinkeys' for + * every path key in the list 'pathkeys'. If there is a matching join key + * (not necessarily unique) for every path key, then the list of + * corresponding join keys and join clauses are returned in the order in + * which the keys matched the path keys. + * + * 'pathkeys' is a list of path keys: + * ( ( (var) (var) ... ) ( (var) ... ) ) + * 'joinkeys' is a list of join keys: + * ( (outer inner) (outer inner) ... ) + * 'joinclauses' is a list of clauses corresponding to the join keys in + * 'joinkeys' + * 'which-subkey' is a flag that selects the desired subkey of a join key + * in 'joinkeys' + * + * Returns the join keys and corresponding join clauses in a list if all + * of the path keys were matched: + * ( + * ( (outerkey0 innerkey0) ... (outerkeyN innerkeyN) ) + * ( clause0 ... clauseN ) + * ) + * and nil otherwise. + * + * Returns a list of matched join keys and a list of matched join clauses + * in matchedJoinClausesPtr. - ay 11/94 + */ +List * +match_pathkeys_joinkeys(List *pathkeys, + List *joinkeys, + List *joinclauses, + int which_subkey, + List **matchedJoinClausesPtr) +{ + List *matched_joinkeys = NIL; + List *matched_joinclauses = NIL; + List *pathkey = NIL; + List *i = NIL; + int matched_joinkey_index = -1; + + foreach(i, pathkeys) { + pathkey = lfirst(i); + matched_joinkey_index = + match_pathkey_joinkeys(pathkey, joinkeys, which_subkey); + + if (matched_joinkey_index != -1 ) { + List *xjoinkey = nth(matched_joinkey_index,joinkeys); + List *joinclause = nth(matched_joinkey_index,joinclauses); + + /* XXX was "push" function */ + matched_joinkeys = lappend(matched_joinkeys,xjoinkey); + matched_joinkeys = nreverse(matched_joinkeys); + + matched_joinclauses = lappend(matched_joinclauses,joinclause); + matched_joinclauses = nreverse(matched_joinclauses); + joinkeys = LispRemove(xjoinkey,joinkeys); + } else { + return(NIL); + } + + } + if(matched_joinkeys==NULL || + length(matched_joinkeys) != length(pathkeys)) { + return NIL; + } + + *matchedJoinClausesPtr = nreverse(matched_joinclauses); + return (nreverse(matched_joinkeys)); +} + +/* + * match-pathkey-joinkeys-- + * Returns the 0-based index into 'joinkeys' of the first joinkey whose + * outer or inner subkey matches any subkey of 'pathkey'. + */ +static int +match_pathkey_joinkeys(List *pathkey, + List *joinkeys, + int which_subkey) +{ + Var *path_subkey; + int pos; + List *i = NIL; + List *x = NIL; + JoinKey *jk; + + foreach(i, pathkey) { + path_subkey = (Var *)lfirst(i); + pos = 0; + foreach(x, joinkeys) { + jk = (JoinKey*)lfirst(x); + if(var_equal(path_subkey, + extract_subkey(jk, which_subkey))) + return(pos); + pos++; + } + } + return(-1); /* no index found */ +} + +/* + * match-paths-joinkeys-- + * Attempts to find a path in 'paths' whose keys match a set of join + * keys 'joinkeys'. To match, + * 1. the path node ordering must equal 'ordering'. + * 2. each subkey of a given path must match(i.e., be(var_equal) to) the + * appropriate subkey of the corresponding join key in 'joinkeys', + * i.e., the Nth path key must match its subkeys against the subkey of + * the Nth join key in 'joinkeys'. + * + * 'joinkeys' is the list of key pairs to which the path keys must be + * matched + * 'ordering' is the ordering of the(outer) path to which 'joinkeys' + * must correspond + * 'paths' is a list of(inner) paths which are to be matched against + * each join key in 'joinkeys' + * 'which-subkey' is a flag that selects the desired subkey of a join key + * in 'joinkeys' + * + * Returns the matching path node if one exists, nil otherwise. + */ +static bool +every_func(List *joinkeys, List *pathkey, int which_subkey) +{ + JoinKey *xjoinkey; + Var *temp; + Var *tempkey = NULL; + bool found = false; + List *i = NIL; + List *j = NIL; + + foreach(i,joinkeys) { + xjoinkey = (JoinKey*)lfirst(i); + found = false; + foreach(j,pathkey) { + temp = (Var*)lfirst((List*)lfirst(j)); + if(temp == NULL) continue; + tempkey = extract_subkey(xjoinkey,which_subkey); + if(var_equal(tempkey, temp)) { + found = true; + break; + } + } + if(found == false) + return(false); + } + return(found); +} + + +/* + * match_paths_joinkeys - + * find the cheapest path that matches the join keys + */ +Path * +match_paths_joinkeys(List *joinkeys, + PathOrder *ordering, + List *paths, + int which_subkey) +{ + Path *matched_path = NULL ; + bool key_match = false; + List *i = NIL; + + foreach(i,paths) { + Path *path = (Path*)lfirst(i); + + key_match = every_func(joinkeys, path->keys, which_subkey); + + if (equal_path_path_ordering(ordering, + &path->p_ordering) && + length(joinkeys) == length(path->keys) && + key_match) { + + if (matched_path) { + if (path->path_cost < matched_path->path_cost) + matched_path = path; + } else { + matched_path = path; + } + } + } + return matched_path; +} + + + +/* + * extract-path-keys-- + * Builds a subkey list for a path by pulling one of the subkeys from + * a list of join keys 'joinkeys' and then finding the var node in the + * target list 'tlist' that corresponds to that subkey. + * + * 'joinkeys' is a list of join key pairs + * 'tlist' is a relation target list + * 'which-subkey' is a flag that selects the desired subkey of a join key + * in 'joinkeys' + * + * Returns a list of pathkeys: ((tlvar1)(tlvar2)...(tlvarN)). + * [I've no idea why they have to be list of lists. Should be fixed. -ay 12/94] + */ +List * +extract_path_keys(List *joinkeys, + List *tlist, + int which_subkey) +{ + List *pathkeys = NIL; + List *jk; + + foreach(jk, joinkeys) { + JoinKey *jkey = (JoinKey*)lfirst(jk); + Var *var, *key; + List *p; + + /* + * find the right Var in the target list for this key + */ + var = (Var*)extract_subkey(jkey, which_subkey); + key = (Var*)matching_tlvar(var, tlist); + + /* + * include it in the pathkeys list if we haven't already done so + */ + foreach(p, pathkeys) { + Var *pkey = lfirst((List*)lfirst(p)); /* XXX fix me */ + if (key == pkey) + break; + } + if (p!=NIL) + continue; /* key already in pathkeys */ + + pathkeys = + lappend(pathkeys, lcons(key,NIL)); + } + return(pathkeys); +} + + +/**************************************************************************** + * NEW PATHKEY FORMATION + ****************************************************************************/ + +/* + * new-join-pathkeys-- + * Find the path keys for a join relation by finding all vars in the list + * of join clauses 'joinclauses' such that: + * (1) the var corresponding to the outer join relation is a + * key on the outer path + * (2) the var appears in the target list of the join relation + * In other words, add to each outer path key the inner path keys that + * are required for qualification. + * + * 'outer-pathkeys' is the list of the outer path's path keys + * 'join-rel-tlist' is the target list of the join relation + * 'joinclauses' is the list of restricting join clauses + * + * Returns the list of new path keys. + * + */ +List * +new_join_pathkeys(List *outer_pathkeys, + List *join_rel_tlist, + List *joinclauses) +{ + List *outer_pathkey = NIL; + List *t_list = NIL; + List *x; + List *i = NIL; + + foreach(i, outer_pathkeys) { + outer_pathkey = lfirst(i); + x = new_join_pathkey(outer_pathkey, NIL, + join_rel_tlist,joinclauses); + if (x!=NIL) { + t_list = lappend(t_list, x); + } + } + return(t_list); +} + +/* + * new-join-pathkey-- + * Finds new vars that become subkeys due to qualification clauses that + * contain any previously considered subkeys. These new subkeys plus the + * subkeys from 'subkeys' form a new pathkey for the join relation. + * + * Note that each returned subkey is the var node found in + * 'join-rel-tlist' rather than the joinclause var node. + * + * 'subkeys' is a list of subkeys for which matching subkeys are to be + * found + * 'considered-subkeys' is the current list of all subkeys corresponding + * to a given pathkey + * + * Returns a new pathkey(list of subkeys). + * + */ +static List * +new_join_pathkey(List *subkeys, + List *considered_subkeys, + List *join_rel_tlist, + List *joinclauses) +{ + List *t_list = NIL; + Var *subkey; + List *i = NIL; + List *matched_subkeys = NIL; + Expr *tlist_key = (Expr*)NULL; + List *newly_considered_subkeys = NIL; + + foreach (i, subkeys) { + subkey = (Var *)lfirst(i); + if(subkey == NULL) + break; /* XXX something is wrong */ + matched_subkeys = + new_matching_subkeys(subkey,considered_subkeys, + join_rel_tlist,joinclauses); + tlist_key = matching_tlvar(subkey,join_rel_tlist); + newly_considered_subkeys = NIL; + + if (tlist_key) { + if(!member(tlist_key, matched_subkeys)) + newly_considered_subkeys = lcons(tlist_key, + matched_subkeys); + } + else { + newly_considered_subkeys = matched_subkeys; + } + + considered_subkeys = + append(considered_subkeys, newly_considered_subkeys); + + t_list = nconc(t_list,newly_considered_subkeys); + } + return(t_list); +} + +/* + * new-matching-subkeys-- + * Returns a list of new subkeys: + * (1) which are not listed in 'considered-subkeys' + * (2) for which the "other" variable in some clause in 'joinclauses' is + * 'subkey' + * (3) which are mentioned in 'join-rel-tlist' + * + * Note that each returned subkey is the var node found in + * 'join-rel-tlist' rather than the joinclause var node. + * + * 'subkey' is the var node for which we are trying to find matching + * clauses + * + * Returns a list of new subkeys. + * + */ +static List * +new_matching_subkeys(Var *subkey, + List *considered_subkeys, + List *join_rel_tlist, + List *joinclauses) +{ + Expr *joinclause = NULL; + List *t_list = NIL; + List *temp = NIL; + List *i = NIL; + Expr *tlist_other_var = (Expr *)NULL; + + foreach(i,joinclauses) { + joinclause = lfirst(i); + tlist_other_var = + matching_tlvar(other_join_clause_var(subkey,joinclause), + join_rel_tlist); + + if(tlist_other_var && + !(member(tlist_other_var,considered_subkeys))) { + + /* XXX was "push" function */ + considered_subkeys = lappend(considered_subkeys, + tlist_other_var); + + /* considered_subkeys = nreverse(considered_subkeys); + XXX -- I am not sure of this. */ + + temp = lcons(tlist_other_var, NIL); + t_list = nconc(t_list,temp); + } + } + return(t_list); +} diff --git a/src/backend/optimizer/path/mergeutils.c b/src/backend/optimizer/path/mergeutils.c new file mode 100644 index 00000000000..d5f0fdcb65b --- /dev/null +++ b/src/backend/optimizer/path/mergeutils.c @@ -0,0 +1,122 @@ +/*------------------------------------------------------------------------- + * + * mergeutils.c-- + * Utilities for finding applicable merge clauses and pathkeys + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/mergeutils.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/clauses.h" +#include "optimizer/ordering.h" + +/* + * group-clauses-by-order-- + * If a join clause node in 'clauseinfo-list' is mergesortable, store + * it within a mergeinfo node containing other clause nodes with the same + * mergesort ordering. + * + * 'clauseinfo-list' is the list of clauseinfo nodes + * 'inner-relid' is the relid of the inner join relation + * + * Returns the new list of mergeinfo nodes. + * + */ +List * +group_clauses_by_order(List *clauseinfo_list, + int inner_relid) +{ + List *mergeinfo_list = NIL; + List *xclauseinfo = NIL; + + foreach (xclauseinfo, clauseinfo_list) { + CInfo *clauseinfo = (CInfo *)lfirst(xclauseinfo); + MergeOrder *merge_ordering = clauseinfo->mergesortorder; + + if (merge_ordering) { + /* + * Create a new mergeinfo node and add it to + * 'mergeinfo-list' if one does not yet exist for this + * merge ordering. + */ + PathOrder p_ordering; + MInfo *xmergeinfo; + Expr *clause = clauseinfo->clause; + Var *leftop = get_leftop (clause); + Var *rightop = get_rightop (clause); + JoinKey *keys; + + p_ordering.ordtype = MERGE_ORDER; + p_ordering.ord.merge = merge_ordering; + xmergeinfo = + match_order_mergeinfo(&p_ordering, mergeinfo_list); + if (inner_relid == leftop->varno) { + keys = makeNode(JoinKey); + keys->outer = rightop; + keys->inner = leftop; + } else { + keys = makeNode(JoinKey); + keys->outer = leftop; + keys->inner = rightop; + } + + if (xmergeinfo==NULL) { + xmergeinfo = makeNode(MInfo); + + xmergeinfo->m_ordering = merge_ordering; + mergeinfo_list = lcons(xmergeinfo, + mergeinfo_list); + } + + ((JoinMethod *)xmergeinfo)->clauses = + lcons(clause, + ((JoinMethod *)xmergeinfo)->clauses); + ((JoinMethod *)xmergeinfo)->jmkeys = + lcons(keys, + ((JoinMethod *)xmergeinfo)->jmkeys); + } + } + return(mergeinfo_list); +} + + +/* + * match-order-mergeinfo-- + * Searches the list 'mergeinfo-list' for a mergeinfo node whose order + * field equals 'ordering'. + * + * Returns the node if it exists. + * + */ +MInfo * +match_order_mergeinfo(PathOrder *ordering, List *mergeinfo_list) +{ + MergeOrder *xmergeorder; + List *xmergeinfo = NIL; + + foreach(xmergeinfo, mergeinfo_list) { + MInfo *mergeinfo = (MInfo*)lfirst(xmergeinfo); + + xmergeorder = mergeinfo->m_ordering; + + if ((ordering->ordtype==MERGE_ORDER && + equal_merge_merge_ordering(ordering->ord.merge, xmergeorder)) || + (ordering->ordtype==SORTOP_ORDER && + equal_path_merge_ordering(ordering->ord.sortop, xmergeorder))) { + + return (mergeinfo); + } + } + return((MInfo*) NIL); +} diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c new file mode 100644 index 00000000000..e040675e6ec --- /dev/null +++ b/src/backend/optimizer/path/orindxpath.c @@ -0,0 +1,271 @@ +/*------------------------------------------------------------------------- + * + * orindxpath.c-- + * Routines to find index paths that match a set of 'or' clauses + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" + +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/paths.h" +#include "optimizer/cost.h" +#include "optimizer/plancat.h" +#include "optimizer/xfunc.h" + +#include "parser/parsetree.h" + + +static void best_or_subclause_indices(Query *root, Rel *rel, List *subclauses, + List *indices, List *examined_indexids, Cost subcost, List *selectivities, + List **indexids, Cost *cost, List **selecs); +static void best_or_subclause_index(Query *root, Rel *rel, Expr *subclause, + List *indices, int *indexid, Cost *cost, Cost *selec); + + +/* + * create-or-index-paths-- + * Creates index paths for indices that match 'or' clauses. + * + * 'rel' is the relation entry for which the paths are to be defined on + * 'clauses' is the list of available restriction clause nodes + * + * Returns a list of these index path nodes. + * + */ +List * +create_or_index_paths(Query *root, + Rel *rel, List *clauses) +{ + List *t_list = NIL; + + if (clauses != NIL) { + CInfo *clausenode = (CInfo *) (lfirst (clauses)); + + /* Check to see if this clause is an 'or' clause, and, if so, + * whether or not each of the subclauses within the 'or' clause has + * been matched by an index (the 'Index field was set in + * (match_or) if no index matches a given subclause, one of the + * lists of index nodes returned by (get_index) will be 'nil'). + */ + if (valid_or_clause(clausenode) && + clausenode->indexids) { + List *temp = NIL; + List *index_list = NIL; + bool index_flag = true; + + index_list = clausenode->indexids; + foreach(temp,index_list) { + if (!temp) + index_flag = false; + } + if (index_flag) { /* used to be a lisp every function */ + IndexPath *pathnode = makeNode(IndexPath); + List *indexids; + Cost cost; + List *selecs; + + best_or_subclause_indices(root, + rel, + clausenode->clause->args, + clausenode->indexids, + NIL, + (Cost)0, + NIL, + &indexids, + &cost, + &selecs); + + pathnode->path.pathtype = T_IndexScan; + pathnode->path.parent = rel; + pathnode->indexqual = + lcons(clausenode,NIL); + pathnode->indexid = indexids; + pathnode->path.path_cost = cost; + + /* copy clauseinfo list into path for expensive + function processing -- JMH, 7/7/92 */ + pathnode->path.locclauseinfo = + set_difference(clauses, + copyObject((Node*) + rel->clauseinfo)); + +#if 0 /* fix xfunc */ + /* add in cost for expensive functions! -- JMH, 7/7/92 */ + if (XfuncMode != XFUNC_OFF) { + ((Path*)pathnode)->path_cost += + xfunc_get_path_cost((Path)pathnode); + } +#endif + clausenode->selectivity = (Cost)floatVal(selecs); + t_list = + lcons(pathnode, + create_or_index_paths(root, rel,lnext(clauses))); + } else { + t_list = create_or_index_paths(root, rel,lnext(clauses)); + } + } + } + + return(t_list); +} + +/* + * best-or-subclause-indices-- + * Determines the best index to be used in conjunction with each subclause + * of an 'or' clause and the cost of scanning a relation using these + * indices. The cost is the sum of the individual index costs. + * + * 'rel' is the node of the relation on which the index is defined + * 'subclauses' are the subclauses of the 'or' clause + * 'indices' are those index nodes that matched subclauses of the 'or' + * clause + * 'examined-indexids' is a list of those index ids to be used with + * subclauses that have already been examined + * 'subcost' is the cost of using the indices in 'examined-indexids' + * 'selectivities' is a list of the selectivities of subclauses that + * have already been examined + * + * Returns a list of the indexids, cost, and selectivities of each + * subclause, e.g., ((i1 i2 i3) cost (s1 s2 s3)), where 'i' is an OID, + * 'cost' is a flonum, and 's' is a flonum. + */ +static void +best_or_subclause_indices(Query *root, + Rel *rel, + List *subclauses, + List *indices, + List *examined_indexids, + Cost subcost, + List *selectivities, + List **indexids, /* return value */ + Cost *cost, /* return value */ + List **selecs) /* return value */ +{ + if (subclauses==NIL) { + *indexids = nreverse(examined_indexids); + *cost = subcost; + *selecs = nreverse(selectivities); + } else { + int best_indexid; + Cost best_cost; + Cost best_selec; + + best_or_subclause_index(root, rel, lfirst(subclauses), lfirst(indices), + &best_indexid, &best_cost, &best_selec); + + best_or_subclause_indices(root, + rel, + lnext(subclauses), + lnext(indices), + lconsi(best_indexid, examined_indexids), + subcost + best_cost, + lcons(makeFloat(best_selec), selectivities), + indexids, + cost, + selecs); + } + return; +} + +/* + * best-or-subclause-index-- + * Determines which is the best index to be used with a subclause of + * an 'or' clause by estimating the cost of using each index and selecting + * the least expensive. + * + * 'rel' is the node of the relation on which the index is defined + * 'subclause' is the subclause + * 'indices' is a list of index nodes that match the subclause + * + * Returns a list (index-id index-subcost index-selectivity) + * (a fixnum, a fixnum, and a flonum respectively). + * + */ +static void +best_or_subclause_index(Query *root, + Rel *rel, + Expr *subclause, + List *indices, + int *retIndexid, /* return value */ + Cost *retCost, /* return value */ + Cost *retSelec) /* return value */ +{ + if (indices != NIL) { + Datum value; + int flag = 0; + Cost subcost; + Rel *index = (Rel *)lfirst (indices); + AttrNumber attno = (get_leftop (subclause))->varattno ; + Oid opno = ((Oper*)subclause->oper)->opno; + bool constant_on_right = non_null((Expr*)get_rightop(subclause)); + float npages, selec; + int subclause_indexid; + Cost subclause_cost; + Cost subclause_selec; + + if(constant_on_right) { + value = ((Const*)get_rightop (subclause))->constvalue; + } else { + value = NameGetDatum(""); + } + if(constant_on_right) { + flag = (_SELEC_IS_CONSTANT_ ||_SELEC_CONSTANT_RIGHT_); + } else { + flag = _SELEC_CONSTANT_RIGHT_; + } + index_selectivity(lfirsti(index->relids), + index->classlist, + lconsi(opno,NIL), + getrelid(lfirsti(rel->relids), + root->rtable), + lconsi(attno,NIL), + lconsi(value,NIL), + lconsi(flag,NIL), + 1, + &npages, + &selec); + + subcost = cost_index((Oid) lfirsti(index->relids), + (int)npages, + (Cost)selec, + rel->pages, + rel->tuples, + index->pages, + index->tuples, + false); + best_or_subclause_index(root, + rel, + subclause, + lnext(indices), + &subclause_indexid, + &subclause_cost, + &subclause_selec); + + if (subclause_indexid==0 || subcost < subclause_cost) { + *retIndexid = lfirsti(index->relids); + *retCost = subcost; + *retSelec = selec; + } else { + *retIndexid = 0; + *retCost = 0.0; + *retSelec = 0.0; + } + } + return; +} diff --git a/src/backend/optimizer/path/predmig.c b/src/backend/optimizer/path/predmig.c new file mode 100644 index 00000000000..2d3b5c5767f --- /dev/null +++ b/src/backend/optimizer/path/predmig.c @@ -0,0 +1,773 @@ +/*------------------------------------------------------------------------- + * + * predmig.c-- + * + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/predmig.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* +** DESCRIPTION +** Main Routines to handle Predicate Migration (i.e. correct optimization +** of queries with expensive functions.) +** +** The reasoning behind some of these algorithms is rather detailed. +** Have a look at Sequoia Tech Report 92/13 for more info. Also +** see Monma and Sidney's paper "Sequencing with Series-Parallel +** Precedence Constraints", in "Mathematics of Operations Research", +** volume 4 (1979), pp. 215-224. +** +** The main thing that this code does that wasn't handled in xfunc.c is +** it considers the possibility that two joins in a stream may not +** be ordered by ascending rank -- in such a scenario, it may be optimal +** to pullup more restrictions than we did via xfunc_try_pullup. +** +** This code in some sense generalizes xfunc_try_pullup; if you +** run postgres -x noprune, you'll turn off xfunc_try_pullup, and this +** code will do everything that xfunc_try_pullup would have, and maybe +** more. However, this results in no pruning, which may slow down the +** optimizer and/or cause the system to run out of memory. +** -- JMH, 11/13/92 +*/ + +#include "nodes/pg_list.h" +#include "nodes/nodes.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "utils/palloc.h" +#include "utils/elog.h" +#include "planner/xfunc.h" +#include "planner/pathnode.h" +#include "planner/internal.h" +#include "planner/cost.h" +#include "planner/keys.h" +#include "planner/tlist.h" +#include "lib/qsort.h" + +#define is_clause(node) (get_cinfo(node)) /* a stream node represents a + clause (not a join) iff it + has a non-NULL cinfo field */ + +static void xfunc_predmig(JoinPath pathnode, Stream streamroot, + Stream laststream, bool *progressp); +static bool xfunc_series_llel(Stream stream); +static bool xfunc_llel_chains(Stream root, Stream bottom); +static Stream xfunc_complete_stream(Stream stream); +static bool xfunc_prdmig_pullup(Stream origstream, Stream pullme, + JoinPath joinpath); +static void xfunc_form_groups(Stream root, Stream bottom); +static void xfunc_free_stream(Stream root); +static Stream xfunc_add_clauses(Stream current); +static void xfunc_setup_group(Stream node, Stream bottom); +static Stream xfunc_streaminsert(CInfo clauseinfo, Stream current, + int clausetype); +static int xfunc_num_relids(Stream node); +static StreamPtr xfunc_get_downjoin(Stream node); +static StreamPtr xfunc_get_upjoin(Stream node); +static Stream xfunc_stream_qsort(Stream root, Stream bottom); +static int xfunc_stream_compare(void *arg1, void *arg2); +static bool xfunc_check_stream(Stream node); +static bool xfunc_in_stream(Stream node, Stream stream); + +/* ----------------- MAIN FUNCTIONS ------------------------ */ +/* +** xfunc_do_predmig +** wrapper for Predicate Migration. It calls xfunc_predmig until no +** more progress is made. +** return value says if any changes were ever made. +*/ +bool xfunc_do_predmig(Path root) +{ + bool progress, changed = false; + + if (is_join(root)) + do + { + progress = false; + Assert(IsA(root,JoinPath)); + xfunc_predmig((JoinPath)root, (Stream)NULL, (Stream)NULL, + &progress); + if (changed && progress) + elog(DEBUG, "Needed to do a second round of predmig!\n"); + if (progress) changed = true; + } while (progress); + return(changed); +} + + +/* + ** xfunc_predmig + ** The main routine for Predicate Migration. It traverses a join tree, + ** and for each root-to-leaf path in the plan tree it constructs a + ** "Stream", which it passes to xfunc_series_llel for optimization. + ** Destructively modifies the join tree (via predicate pullup). + */ +static void +xfunc_predmig(JoinPath pathnode, /* root of the join tree */ + Stream streamroot, + Stream laststream, /* for recursive calls -- these are + the root of the stream under + construction, and the lowest node + created so far */ + bool *progressp) +{ + Stream newstream; + + /* + ** traverse the join tree dfs-style, constructing a stream as you go. + ** When you hit a scan node, pass the stream off to xfunc_series_llel. + */ + + /* sanity check */ + if ((!streamroot && laststream) || + (streamroot && !laststream)) + elog(WARN, "called xfunc_predmig with bad inputs"); + if (streamroot) Assert(xfunc_check_stream(streamroot)); + + /* add path node to stream */ + newstream = RMakeStream(); + if (!streamroot) + streamroot = newstream; + set_upstream(newstream, (StreamPtr)laststream); + if (laststream) + set_downstream(laststream, (StreamPtr)newstream); + set_downstream(newstream, (StreamPtr)NULL); + set_pathptr(newstream, (pathPtr)pathnode); + set_cinfo(newstream, (CInfo)NULL); + set_clausetype(newstream, XFUNC_UNKNOWN); + + /* base case: we're at a leaf, call xfunc_series_llel */ + if (!is_join(pathnode)) + { + /* form a fleshed-out copy of the stream */ + Stream fullstream = xfunc_complete_stream(streamroot); + + /* sort it via series-llel */ + if (xfunc_series_llel(fullstream)) + *progressp = true; + + /* free up the copy */ + xfunc_free_stream(fullstream); + } + else + { + /* visit left child */ + xfunc_predmig((JoinPath)get_outerjoinpath(pathnode), + streamroot, newstream, progressp); + + /* visit right child */ + xfunc_predmig((JoinPath)get_innerjoinpath(pathnode), + streamroot, newstream, progressp); + } + + /* remove this node */ + if (get_upstream(newstream)) + set_downstream((Stream)get_upstream(newstream), (StreamPtr)NULL); + pfree(newstream); +} + +/* + ** xfunc_series_llel + ** A flavor of Monma and Sidney's Series-Parallel algorithm. + ** Traverse stream downwards. When you find a node with restrictions on it, + ** call xfunc_llel_chains on the substream from root to that node. + */ +static bool xfunc_series_llel(Stream stream) +{ + Stream temp, next; + bool progress = false; + + for (temp = stream; temp != (Stream)NULL; temp = next) + { + next = (Stream)xfunc_get_downjoin(temp); + /* + ** if there are restrictions/secondary join clauses above this + ** node, call xfunc_llel_chains + */ + if (get_upstream(temp) && is_clause((Stream)get_upstream(temp))) + if (xfunc_llel_chains(stream, temp)) + progress = true; + } + return(progress); +} + +/* + ** xfunc_llel_chains + ** A flavor of Monma and Sidney's Parallel Chains algorithm. + ** Given a stream which has been well-ordered except for its lowermost + ** restrictions/2-ary joins, pull up the restrictions/2-arys as appropriate. + ** What that means here is to form groups in the chain above the lowest + ** join node above bottom inclusive, and then take all the restrictions + ** following bottom, and try to pull them up as far as possible. + */ +static bool xfunc_llel_chains(Stream root, Stream bottom) +{ + bool progress = false; + Stream origstream; + Stream tmpstream, pathstream; + Stream rootcopy = root; + + Assert(xfunc_check_stream(root)); + + /* xfunc_prdmig_pullup will need an unmodified copy of the stream */ + origstream = (Stream)copyObject((Node)root); + + /* form groups among ill-ordered nodes */ + xfunc_form_groups(root, bottom); + + /* sort chain by rank */ + Assert(xfunc_in_stream(bottom, root)); + rootcopy = xfunc_stream_qsort(root, bottom); + + /* + ** traverse sorted stream -- if any restriction has moved above a join, + ** we must pull it up in the plan. That is, make plan tree + ** reflect order of sorted stream. + */ + for (tmpstream = rootcopy, + pathstream = (Stream)xfunc_get_downjoin(rootcopy); + tmpstream != (Stream)NULL && pathstream != (Stream)NULL; + tmpstream = (Stream)get_downstream(tmpstream)) + { + if (is_clause(tmpstream) + && get_pathptr(pathstream) != get_pathptr(tmpstream)) + { + /* + ** If restriction moved above a Join after sort, we pull it + ** up in the join plan. + ** If restriction moved down, we ignore it. + ** This is because Joey's Sequoia paper proves that + ** restrictions should never move down. If this + ** one were moved down, it would violate "semantic correctness", + ** i.e. it would be lower than the attributes it references. + */ + Assert(xfunc_num_relids(pathstream)>xfunc_num_relids(tmpstream)); + progress = + xfunc_prdmig_pullup(origstream, tmpstream, + (JoinPath)get_pathptr(pathstream)); + } + if (get_downstream(tmpstream)) + pathstream = + (Stream)xfunc_get_downjoin((Stream)get_downstream(tmpstream)); + } + + /* free up origstream */ + xfunc_free_stream(origstream); + return(progress); +} + +/* + ** xfunc_complete_stream -- + ** Given a stream composed of join nodes only, make a copy containing the + ** join nodes along with the associated restriction nodes. + */ +static Stream xfunc_complete_stream(Stream stream) +{ + Stream tmpstream, copystream, curstream = (Stream)NULL; + + copystream = (Stream)copyObject((Node)stream); + Assert(xfunc_check_stream(copystream)); + + curstream = copystream; + Assert(!is_clause(curstream)); + + /* curstream = (Stream)xfunc_get_downjoin(curstream); */ + + while(curstream != (Stream)NULL) + { + xfunc_add_clauses(curstream); + curstream = (Stream)xfunc_get_downjoin(curstream); + } + + /* find top of stream and return it */ + for (tmpstream = copystream; get_upstream(tmpstream) != (StreamPtr)NULL; + tmpstream = (Stream)get_upstream(tmpstream)) + /* no body in for loop */; + + return(tmpstream); +} + +/* + ** xfunc_prdmig_pullup + ** pullup a clause in a path above joinpath. Since the JoinPath tree + ** doesn't have upward pointers, it's difficult to deal with. Thus we + ** require the original stream, which maintains pointers to all the path + ** nodes. We use the original stream to find out what joins are + ** above the clause. + */ +static bool +xfunc_prdmig_pullup(Stream origstream, Stream pullme, JoinPath joinpath) +{ + CInfo clauseinfo = get_cinfo(pullme); + bool progress = false; + Stream upjoin, orignode, temp; + int whichchild; + + /* find node in origstream that contains clause */ + for (orignode = origstream; + orignode != (Stream) NULL + && get_cinfo(orignode) != clauseinfo; + orignode = (Stream)get_downstream(orignode)) + /* empty body in for loop */ ; + if (!orignode) + elog(WARN, "Didn't find matching node in original stream"); + + + /* pull up this node as far as it should go */ + for (upjoin = (Stream)xfunc_get_upjoin(orignode); + upjoin != (Stream)NULL + && (JoinPath)get_pathptr((Stream)xfunc_get_downjoin(upjoin)) + != joinpath; + upjoin = (Stream)xfunc_get_upjoin(upjoin)) + { +#ifdef DEBUG + elog(DEBUG, "pulling up in xfunc_predmig_pullup!"); +#endif + /* move clause up in path */ + if (get_pathptr((Stream)get_downstream(upjoin)) + == (pathPtr)get_outerjoinpath((JoinPath)get_pathptr(upjoin))) + whichchild = OUTER; + else whichchild = INNER; + clauseinfo = xfunc_pullup((Path)get_pathptr((Stream)get_downstream(upjoin)), + (JoinPath)get_pathptr(upjoin), + clauseinfo, + whichchild, + get_clausetype(orignode)); + set_pathptr(pullme, get_pathptr(upjoin)); + /* pullme has been moved into locclauseinfo */ + set_clausetype(pullme, XFUNC_LOCPRD); + + /* + ** xfunc_pullup makes new path nodes for children of + ** get_pathptr(current). We must modify the stream nodes to point + ** to these path nodes + */ + if (whichchild == OUTER) + { + for(temp = (Stream)get_downstream(upjoin); is_clause(temp); + temp = (Stream)get_downstream(temp)) + set_pathptr + (temp, (pathPtr) + get_outerjoinpath((JoinPath)get_pathptr(upjoin))); + set_pathptr + (temp, + (pathPtr)get_outerjoinpath((JoinPath)get_pathptr(upjoin))); + } + else + { + for(temp = (Stream)get_downstream(upjoin); is_clause(temp); + temp = (Stream)get_downstream(temp)) + set_pathptr + (temp, (pathPtr) + get_innerjoinpath((JoinPath)get_pathptr(upjoin))); + set_pathptr + (temp, (pathPtr) + get_innerjoinpath((JoinPath)get_pathptr(upjoin))); + } + progress = true; + } + if (!progress) + elog(DEBUG, "didn't succeed in pulling up in xfunc_prdmig_pullup"); + return(progress); +} + +/* + ** xfunc_form_groups -- + ** A group is a pair of stream nodes a,b such that a is constrained to + ** precede b (for instance if a and b are both joins), but rank(a) > rank(b). + ** In such a situation, Monma and Sidney prove that no clauses should end + ** up between a and b, and therefore we may treat them as a group, with + ** selectivity equal to the product of their selectivities, and cost + ** equal to the cost of the first plus the selectivity of the first times the + ** cost of the second. We define each node to be in a group by itself, + ** and then repeatedly find adjacent groups which are ordered by descending + ** rank, and make larger groups. You know that two adjacent nodes are in a + ** group together if the lower has groupup set to true. They will both have + ** the same groupcost and groupsel (since they're in the same group!) + */ +static void xfunc_form_groups(Query* queryInfo, Stream root, Stream bottom) +{ + Stream temp, parent; + int lowest = xfunc_num_relids((Stream)xfunc_get_upjoin(bottom)); + bool progress; + LispValue primjoin; + int whichchild; + + if (!lowest) return; /* no joins in stream, so no groups */ + + /* initialize groups to be single nodes */ + for (temp = root; + temp != (Stream)NULL && temp != bottom; + temp = (Stream)get_downstream(temp)) + { + /* if a Join node */ + if (!is_clause(temp)) + { + if (get_pathptr((Stream)get_downstream(temp)) + == (pathPtr)get_outerjoinpath((JoinPath)get_pathptr(temp))) + whichchild = OUTER; + else whichchild = INNER; + set_groupcost(temp, + xfunc_join_expense((JoinPath)get_pathptr(temp), + whichchild)); + if (primjoin = xfunc_primary_join((JoinPath)get_pathptr(temp))) + { + set_groupsel(temp, + compute_clause_selec(queryInfo, + primjoin, NIL)); + } + else + { + set_groupsel(temp,1.0); + } + } + else /* a restriction, or 2-ary join pred */ + { + set_groupcost(temp, + xfunc_expense(queryInfo, + get_clause(get_cinfo(temp)))); + set_groupsel(temp, + compute_clause_selec(queryInfo, + get_clause(get_cinfo(temp)), + NIL)); + } + set_groupup(temp,false); + } + + /* make passes upwards, forming groups */ + do + { + progress = false; + for (temp = (Stream)get_upstream(bottom); + temp != (Stream)NULL; + temp = (Stream)get_upstream(temp)) + { + /* check for grouping with node upstream */ + if (!get_groupup(temp) && /* not already grouped */ + (parent = (Stream)get_upstream(temp)) != (Stream)NULL && + /* temp is a join or temp is the top of a group */ + (is_join((Path)get_pathptr(temp)) || + get_downstream(temp) && + get_groupup((Stream)get_downstream(temp))) && + get_grouprank(parent) < get_grouprank(temp)) + { + progress = true; /* we formed a new group */ + set_groupup(temp,true); + set_groupcost(temp, + get_groupcost(temp) + + get_groupsel(temp) * get_groupcost(parent)); + set_groupsel(temp,get_groupsel(temp) * get_groupsel(parent)); + + /* fix costs and sels of all members of group */ + xfunc_setup_group(temp, bottom); + } + } + } while(progress); +} + + +/* ------------------- UTILITY FUNCTIONS ------------------------- */ + +/* + ** xfunc_free_stream -- + ** walk down a stream and pfree it + */ +static void xfunc_free_stream(Stream root) +{ + Stream cur, next; + + Assert(xfunc_check_stream(root)); + + if (root != (Stream)NULL) + for (cur = root; cur != (Stream)NULL; cur = next) + { + next = (Stream)get_downstream(cur); + pfree(cur); + } +} + +/* + ** xfunc_add<_clauses + ** find any clauses above current, and insert them into stream as + ** appropriate. Return uppermost clause inserted, or current if none. + */ +static Stream xfunc_add_clauses(Stream current) +{ + Stream topnode = current; + LispValue temp; + LispValue primjoin; + + /* first add in the local clauses */ + foreach(temp, get_locclauseinfo((Path)get_pathptr(current))) + { + topnode = + xfunc_streaminsert((CInfo)lfirst(temp), topnode, + XFUNC_LOCPRD); + } + + /* and add in the join clauses */ + if (IsA(get_pathptr(current),JoinPath)) + { + primjoin = xfunc_primary_join((JoinPath)get_pathptr(current)); + foreach(temp, get_pathclauseinfo((JoinPath)get_pathptr(current))) + { + if (!equal(get_clause((CInfo)lfirst(temp)), primjoin)) + topnode = + xfunc_streaminsert((CInfo)lfirst(temp), topnode, + XFUNC_JOINPRD); + } + } + return(topnode); +} + + +/* + ** xfunc_setup_group + ** find all elements of stream that are grouped with node and are above + ** bottom, and set their groupcost and groupsel to be the same as node's. + */ +static void xfunc_setup_group(Stream node, Stream bottom) +{ + Stream temp; + + if (node != bottom) + /* traverse downwards */ + for (temp = (Stream)get_downstream(node); + temp != (Stream)NULL && temp != bottom; + temp = (Stream)get_downstream(temp)) + { + if (!get_groupup(temp)) break; + else + { + set_groupcost(temp, get_groupcost(node)); + set_groupsel(temp, get_groupsel(node)); + } + } + + /* traverse upwards */ + for (temp = (Stream)get_upstream(node); temp != (Stream)NULL; + temp = (Stream)get_upstream(temp)) + { + if (!get_groupup((Stream)get_downstream(temp))) break; + else + { + set_groupcost(temp, get_groupcost(node)); + set_groupsel(temp, get_groupsel(node)); + } + } +} + + +/* + ** xfunc_streaminsert + ** Make a new Stream node to hold clause, and insert it above current. + ** Return new node. + */ +static Stream +xfunc_streaminsert(CInfo clauseinfo, + Stream current, + int clausetype) /* XFUNC_LOCPRD or XFUNC_JOINPRD */ +{ + Stream newstream = RMakeStream(); + set_upstream(newstream, get_upstream(current)); + if (get_upstream(current)) + set_downstream((Stream)(get_upstream(current)), (StreamPtr)newstream); + set_upstream(current, (StreamPtr)newstream); + set_downstream(newstream, (StreamPtr)current); + set_pathptr(newstream, get_pathptr(current)); + set_cinfo(newstream, clauseinfo); + set_clausetype(newstream, clausetype); + return(newstream); +} + +/* + ** Given a Stream node, find the number of relids referenced in the pathnode + ** associated with the stream node. The number of relids gives a unique + ** ordering on the joins in a stream, which we use to compare the height of + ** join nodes. + */ +static int xfunc_num_relids(Stream node) +{ + if (!node || !IsA(get_pathptr(node),JoinPath)) + return(0); + else return(length + (get_relids(get_parent((JoinPath)get_pathptr(node))))); +} + +/* + ** xfunc_get_downjoin -- + ** Given a stream node, find the next lowest node which points to a + ** join predicate or a scan node. + */ +static StreamPtr xfunc_get_downjoin(Stream node) +{ + Stream temp; + + if (!is_clause(node)) /* if this is a join */ + node = (Stream)get_downstream(node); + for (temp = node; temp && is_clause(temp); + temp = (Stream)get_downstream(temp)) + /* empty body in for loop */ ; + + return((StreamPtr)temp); +} + +/* + ** xfunc_get_upjoin -- + ** same as above, but upwards. + */ +static StreamPtr xfunc_get_upjoin(Stream node) +{ + Stream temp; + + if (!is_clause(node)) /* if this is a join */ + node = (Stream)get_upstream(node); + for (temp = node; temp && is_clause(temp); + temp = (Stream)get_upstream(temp)) + /* empty body in for loop */ ; + + return((StreamPtr)temp); +} + +/* + ** xfunc_stream_qsort -- + ** Given a stream, sort by group rank the elements in the stream from the + ** node "bottom" up. DESTRUCTIVELY MODIFIES STREAM! Returns new root. + */ +static Stream xfunc_stream_qsort(Stream root, Stream bottom) +{ + int i; + size_t num; + Stream *nodearray, output; + Stream tmp; + + /* find size of list */ + for (num = 0, tmp = root; tmp != bottom; + tmp = (Stream)get_downstream(tmp)) + num ++; + if (num <= 1) return (root); + + /* copy elements of the list into an array */ + nodearray = (Stream *) palloc(num * sizeof(Stream)); + + for (tmp = root, i = 0; tmp != bottom; + tmp = (Stream)get_downstream(tmp), i++) + nodearray[i] = tmp; + + /* sort the array */ + pg_qsort(nodearray, num, sizeof(LispValue), xfunc_stream_compare); + + /* paste together the array elements */ + output = nodearray[num - 1]; + set_upstream(output, (StreamPtr)NULL); + for (i = num - 2; i >= 0; i--) + { + set_downstream(nodearray[i+1], (StreamPtr)nodearray[i]); + set_upstream(nodearray[i], (StreamPtr)nodearray[i+1]); + } + set_downstream(nodearray[0], (StreamPtr)bottom); + if (bottom) + set_upstream(bottom, (StreamPtr)nodearray[0]); + + Assert(xfunc_check_stream(output)); + return(output); +} + +/* + ** xfunc_stream_compare + ** comparison function for xfunc_stream_qsort. + ** Compare nodes by group rank. If group ranks are equal, ensure that + ** join nodes appear in same order as in plan tree. + */ +static int xfunc_stream_compare(void *arg1, void *arg2) +{ + Stream stream1 = *(Stream *) arg1; + Stream stream2 = *(Stream *) arg2; + Cost rank1, rank2; + + rank1 = get_grouprank(stream1); + rank2 = get_grouprank(stream2); + + if (rank1 > rank2) return(1); + else if (rank1 < rank2) return(-1); + else + { + if (is_clause(stream1) && is_clause(stream2)) + return(0); /* doesn't matter what order if both are restrictions */ + else if (!is_clause(stream1) && !is_clause(stream2)) + { + if (xfunc_num_relids(stream1) < xfunc_num_relids(stream2)) + return(-1); + else return(1); + } + else if (is_clause(stream1) && !is_clause(stream2)) + { + if (xfunc_num_relids(stream1) == xfunc_num_relids(stream2)) + /* stream1 is a restriction over stream2 */ + return(1); + else return(-1); + } + else if (!is_clause(stream1) && is_clause(stream2)) + { + /* stream2 is a restriction over stream1: never push down */ + return(-1); + } + } +} + +/* ------------------ DEBUGGING ROUTINES ---------------------------- */ + +/* + ** Make sure all pointers in stream make sense. Make sure no joins are + ** out of order. + */ +static bool xfunc_check_stream(Stream node) +{ + Stream temp; + int numrelids, tmp; + + /* set numrelids higher than max */ + if (!is_clause(node)) + numrelids = xfunc_num_relids(node) + 1; + else if (xfunc_get_downjoin(node)) + numrelids = xfunc_num_relids((Stream)xfunc_get_downjoin(node)) + 1; + else numrelids = 1; + + for (temp = node; get_downstream(temp); temp = (Stream)get_downstream(temp)) + { + if ((Stream)get_upstream((Stream)get_downstream(temp)) != temp) + { + elog(WARN, "bad pointers in stream"); + return(false); + } + if (!is_clause(temp)) + { + if ((tmp = xfunc_num_relids(temp)) >= numrelids) + { + elog(WARN, "Joins got reordered!"); + return(false); + } + numrelids = tmp; + } + } + + return(true); +} + +/* + ** xfunc_in_stream + ** check if node is in stream + */ +static bool xfunc_in_stream(Stream node, Stream stream) +{ + Stream temp; + + for (temp = stream; temp; temp = (Stream)get_downstream(temp)) + if (temp == node) return(1); + return(0); +} diff --git a/src/backend/optimizer/path/prune.c b/src/backend/optimizer/path/prune.c new file mode 100644 index 00000000000..70f9b209e0c --- /dev/null +++ b/src/backend/optimizer/path/prune.c @@ -0,0 +1,203 @@ +/*------------------------------------------------------------------------- + * + * prune.c-- + * Routines to prune redundant paths and relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/prune.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" + +#include "utils/elog.h" + + +static List *prune_joinrel(Rel *rel, List *other_rels); + +/* + * prune-joinrels-- + * Removes any redundant relation entries from a list of rel nodes + * 'rel-list'. + * + * Returns the resulting list. + * + */ +List *prune_joinrels(List *rel_list) +{ + List *temp_list = NIL; + + if (rel_list != NIL) { + temp_list = lcons(lfirst(rel_list), + prune_joinrels(prune_joinrel((Rel*)lfirst(rel_list), + lnext(rel_list)))); + } + return(temp_list); +} + +/* + * prune-joinrel-- + * Prunes those relations from 'other-rels' that are redundant with + * 'rel'. A relation is redundant if it is built up of the same + * relations as 'rel'. Paths for the redundant relation are merged into + * the pathlist of 'rel'. + * + * Returns a list of non-redundant relations, and sets the pathlist field + * of 'rel' appropriately. + * + */ +static List * +prune_joinrel(Rel *rel, List *other_rels) +{ + List *i = NIL; + List *t_list = NIL; + List *temp_node = NIL; + Rel *other_rel = (Rel *)NULL; + + foreach(i, other_rels) { + other_rel = (Rel*)lfirst(i); + if(same(rel->relids, other_rel->relids)) { + rel->pathlist = add_pathlist(rel, + rel->pathlist, + other_rel->pathlist); + t_list = nconc(t_list, NIL); /* XXX is this right ? */ + } else { + temp_node = lcons(other_rel, NIL); + t_list = nconc(t_list,temp_node); + } + } + return(t_list); +} + +/* + * prune-rel-paths-- + * For each relation entry in 'rel-list' (which corresponds to a join + * relation), set pointers to the unordered path and cheapest paths + * (if the unordered path isn't the cheapest, it is pruned), and + * reset the relation's size field to reflect the join. + * + * Returns nothing of interest. + * + */ +void +prune_rel_paths(List *rel_list) +{ + List *x = NIL; + List *y = NIL; + Path *path; + Rel *rel = (Rel*)NULL; + JoinPath *cheapest = (JoinPath*)NULL; + + foreach(x, rel_list) { + rel = (Rel*)lfirst(x); + foreach(y, rel->pathlist) { + path = (Path*)lfirst(y); + + if(!path->p_ordering.ord.sortop) { + break; + } + } + cheapest = (JoinPath*)prune_rel_path(rel, path); + if (IsA_JoinPath(cheapest)) + { + rel->size = compute_joinrel_size(cheapest); + } + else + elog(WARN, "non JoinPath called"); + } +} + + +/* + * prune-rel-path-- + * Compares the unordered path for a relation with the cheapest path. If + * the unordered path is not cheapest, it is pruned. + * + * Resets the pointers in 'rel' for unordered and cheapest paths. + * + * Returns the cheapest path. + * + */ +Path * +prune_rel_path(Rel *rel, Path *unorderedpath) +{ + Path *cheapest = set_cheapest(rel, rel->pathlist); + + /* don't prune if not pruneable -- JMH, 11/23/92 */ + if(unorderedpath != cheapest + && rel->pruneable) { + + rel->unorderedpath = (Path *)NULL; + rel->pathlist = lremove(unorderedpath, rel->pathlist); + } else { + rel->unorderedpath = (Path *)unorderedpath; + } + + return(cheapest); +} + +/* + * merge-joinrels-- + * Given two lists of rel nodes that are already + * pruned, merge them into one pruned rel node list + * + * 'rel-list1' and + * 'rel-list2' are the rel node lists + * + * Returns one pruned rel node list + */ +List * +merge_joinrels(List *rel_list1, List *rel_list2) +{ + List *xrel = NIL; + + foreach(xrel,rel_list1) { + Rel *rel = (Rel*)lfirst(xrel); + rel_list2 = prune_joinrel(rel,rel_list2); + } + return(append(rel_list1, rel_list2)); +} + +/* + * prune_oldrels-- + * If all the joininfo's in a rel node are inactive, + * that means that this node has been joined into + * other nodes in all possible ways, therefore + * this node can be discarded. If not, it will cause + * extra complexity of the optimizer. + * + * old_rels is a list of rel nodes + * + * Returns a new list of rel nodes + */ +List *prune_oldrels(List *old_rels) +{ + Rel *rel; + List *joininfo_list, *xjoininfo; + + if(old_rels == NIL) + return(NIL); + + rel = (Rel*)lfirst(old_rels); + joininfo_list = rel->joininfo; + if(joininfo_list == NIL) + return (lcons(rel, prune_oldrels(lnext(old_rels)))); + + foreach(xjoininfo, joininfo_list) { + JInfo *joininfo = (JInfo*)lfirst(xjoininfo); + if(!joininfo->inactive) + return (lcons(rel, prune_oldrels(lnext(old_rels)))); + } + return(prune_oldrels(lnext(old_rels))); +} diff --git a/src/backend/optimizer/path/xfunc.c b/src/backend/optimizer/path/xfunc.c new file mode 100644 index 00000000000..405b3b77f00 --- /dev/null +++ b/src/backend/optimizer/path/xfunc.c @@ -0,0 +1,1360 @@ +/*------------------------------------------------------------------------- + * + * xfunc.c-- + * Utility routines to handle expensive function optimization. + * Includes xfunc_trypullup(), which attempts early pullup of predicates + * to allow for maximal pruning. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/xfunc.c,v 1.1.1.1 1996/07/09 06:21:36 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef WIN32 +#include <math.h> /* for MAXFLOAT on most systems */ +#else +#include <float.h> +#define MAXFLOAT DBL_MAX +#endif /* WIN32 */ + +#include <values.h> /* for MAXFLOAT on SunOS */ +#include <string.h> + +#include "postgres.h" +#include "nodes/pg_list.h" +#include "nodes/nodes.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/syscache.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "utils/syscache.h" +#include "catalog/pg_language.h" +#include "planner/xfunc.h" +#include "planner/clauses.h" +#include "planner/pathnode.h" +#include "planner/internal.h" +#include "planner/cost.h" +#include "planner/keys.h" +#include "planner/tlist.h" +#include "lib/lispsort.h" +#include "access/heapam.h" +#include "tcop/dest.h" +#include "storage/buf_internals.h" /* for NBuffers */ +#include "optimizer/tlist.h" /* for get_expr */ + +#define ever ; 1 ; + +/* local funcs */ +static int xfunc_card_unreferenced(Query *queryInfo, + Expr *clause, Relid referenced); */ + +/* +** xfunc_trypullup -- +** Preliminary pullup of predicates, to allow for maximal pruning. +** Given a relation, check each of its paths and see if you can +** pullup clauses from its inner and outer. +*/ + +void xfunc_trypullup(Rel rel) +{ + LispValue y; /* list ptr */ + CInfo maxcinfo; /* The CInfo to pull up, as calculated by + xfunc_shouldpull() */ + JoinPath curpath; /* current path in list */ + int progress; /* has progress been made this time through? */ + int clausetype; + + do { + progress = false; /* no progress yet in this iteration */ + foreach(y, get_pathlist(rel)) { + curpath = (JoinPath)lfirst(y); + + /* + ** for each operand, attempt to pullup predicates until first + ** failure. + */ + for(ever) { + /* No, the following should NOT be '==' !! */ + if (clausetype = + xfunc_shouldpull((Path)get_innerjoinpath(curpath), + curpath, INNER, &maxcinfo)) { + + xfunc_pullup((Path)get_innerjoinpath(curpath), + curpath, maxcinfo, INNER, clausetype); + progress = true; + }else + break; + } + for(ever) { + + /* No, the following should NOT be '==' !! */ + if (clausetype = + xfunc_shouldpull((Path)get_outerjoinpath(curpath), + curpath, OUTER, &maxcinfo)) { + + xfunc_pullup((Path)get_outerjoinpath(curpath), + curpath, maxcinfo, OUTER, clausetype); + progress = true; + }else + break; + } + + /* + ** make sure the unpruneable flag bubbles up, i.e. + ** if anywhere below us in the path pruneable is false, + ** then pruneable should be false here + */ + if (get_pruneable(get_parent(curpath)) && + (!get_pruneable(get_parent + ((Path)get_innerjoinpath(curpath))) || + !get_pruneable(get_parent((Path) + get_outerjoinpath(curpath))))) { + + set_pruneable(get_parent(curpath),false); + progress = true; + } + } + } while(progress); +} + +/* + ** xfunc_shouldpull -- + ** find clause with highest rank, and decide whether to pull it up + ** from child to parent. Currently we only pullup secondary join clauses + ** that are in the pathclauseinfo. Secondary hash and sort clauses are + ** left where they are. + ** If we find an expensive function but decide *not* to pull it up, + ** we'd better set the unpruneable flag. -- JMH, 11/11/92 + ** + ** Returns: 0 if nothing left to pullup + ** XFUNC_LOCPRD if a local predicate is to be pulled up + ** XFUNC_JOINPRD if a secondary join predicate is to be pulled up + */ +int xfunc_shouldpull(Query* queryInfo, + Path childpath, + JoinPath parentpath, + int whichchild, + CInfo *maxcinfopt) /* Out: pointer to clause to pullup */ +{ + LispValue clauselist, tmplist; /* lists of clauses */ + CInfo maxcinfo; /* clause to pullup */ + LispValue primjoinclause /* primary join clause */ + = xfunc_primary_join(parentpath); + Cost tmprank, maxrank = (-1 * MAXFLOAT); /* ranks of clauses */ + Cost joinselec = 0; /* selectivity of the join predicate */ + Cost joincost = 0; /* join cost + primjoinclause cost */ + int retval = XFUNC_LOCPRD; + + clauselist = get_locclauseinfo(childpath); + + if (clauselist != LispNil) { + /* find local predicate with maximum rank */ + for (tmplist = clauselist, + maxcinfo = (CInfo) lfirst(tmplist), + maxrank = xfunc_rank(get_clause(maxcinfo)); + tmplist != LispNil; + tmplist = lnext(tmplist)) { + + if ((tmprank = xfunc_rank(get_clause((CInfo)lfirst(tmplist)))) + > maxrank) { + maxcinfo = (CInfo) lfirst(tmplist); + maxrank = tmprank; + } + } + } + + /* + ** If child is a join path, and there are multiple join clauses, + ** see if any join clause has even higher rank than the highest + ** local predicate + */ + if (is_join(childpath) && xfunc_num_join_clauses((JoinPath)childpath) > 1) + for (tmplist = get_pathclauseinfo((JoinPath)childpath); + tmplist != LispNil; + tmplist = lnext(tmplist)) { + + if (tmplist != LispNil && + (tmprank = xfunc_rank(get_clause((CInfo) lfirst(tmplist)))) + > maxrank) { + maxcinfo = (CInfo) lfirst(tmplist); + maxrank = tmprank; + retval = XFUNC_JOINPRD; + } + } + if (maxrank == (-1 * MAXFLOAT)) /* no expensive clauses */ + return(0); + + /* + ** Pullup over join if clause is higher rank than join, or if + ** join is nested loop and current path is inner child (note that + ** restrictions on the inner of a nested loop don't buy you anything -- + ** you still have to scan the entire inner relation each time). + ** Note that the cost of a secondary join clause is only what's + ** calculated by xfunc_expense(), since the actual joining + ** (i.e. the usual path_cost) is paid for by the primary join clause. + */ + if (primjoinclause != LispNil) { + joinselec = compute_clause_selec(queryInfo, primjoinclause, LispNil); + joincost = xfunc_join_expense(parentpath, whichchild); + + if (XfuncMode == XFUNC_PULLALL || + (XfuncMode != XFUNC_WAIT && + ((joincost != 0 && + (maxrank = xfunc_rank(get_clause(maxcinfo))) > + ((joinselec - 1.0) / joincost)) + || (joincost == 0 && joinselec < 1) + || (!is_join(childpath) + && (whichchild == INNER) + && IsA(parentpath,JoinPath) + && !IsA(parentpath,HashPath) + && !IsA(parentpath,MergePath))))) { + + *maxcinfopt = maxcinfo; + return(retval); + + }else if (maxrank != -(MAXFLOAT)) { + /* + ** we've left an expensive restriction below a join. Since + ** we may pullup this restriction in predmig.c, we'd best + ** set the Rel of this join to be unpruneable + */ + set_pruneable(get_parent(parentpath), false); + /* and fall through */ + } + } + return(0); +} + + +/* + ** xfunc_pullup -- + ** move clause from child pathnode to parent pathnode. This operation + ** makes the child pathnode produce a larger relation than it used to. + ** This means that we must construct a new Rel just for the childpath, + ** although this Rel will not be added to the list of Rels to be joined up + ** in the query; it's merely a parent for the new childpath. + ** We also have to fix up the path costs of the child and parent. + ** + ** Now returns a pointer to the new pulled-up CInfo. -- JMH, 11/18/92 + */ +CInfo xfunc_pullup(Query* queryInfo, + Path childpath, + JoinPath parentpath, + CInfo cinfo, /* clause to pull up */ + int whichchild,/* whether child is INNER or OUTER of join */ + int clausetype)/* whether clause to pull is join or local */ +{ + Path newkid; + Rel newrel; + Cost pulled_selec; + Cost cost; + CInfo newinfo; + + /* remove clause from childpath */ + newkid = (Path)copyObject((Node)childpath); + if (clausetype == XFUNC_LOCPRD) { + set_locclauseinfo(newkid, + xfunc_LispRemove((LispValue)cinfo, + (List)get_locclauseinfo(newkid))); + }else { + set_pathclauseinfo + ((JoinPath)newkid, + xfunc_LispRemove((LispValue)cinfo, + (List)get_pathclauseinfo((JoinPath)newkid))); + } + + /* + ** give the new child path its own Rel node that reflects the + ** lack of the pulled-up predicate + */ + pulled_selec = compute_clause_selec(queryInfo, + get_clause(cinfo), LispNil); + xfunc_copyrel(get_parent(newkid), &newrel); + set_parent(newkid, newrel); + set_pathlist(newrel, lcons(newkid, NIL)); + set_unorderedpath(newrel, (PathPtr)newkid); + set_cheapestpath(newrel, (PathPtr)newkid); + set_size(newrel, + (Count)((Cost)get_size(get_parent(childpath)) / pulled_selec)); + + /* + ** fix up path cost of newkid. To do this we subtract away all the + ** xfunc_costs of childpath, then recompute the xfunc_costs of newkid + */ + cost = get_path_cost(newkid) - xfunc_get_path_cost(childpath); + Assert(cost >= 0); + set_path_cost(newkid, cost); + cost = get_path_cost(newkid) + xfunc_get_path_cost(newkid); + set_path_cost(newkid, cost); + + /* + ** We copy the cinfo, since it may appear in other plans, and we're going + ** to munge it. -- JMH, 7/22/92 + */ + newinfo = (CInfo)copyObject((Node)cinfo); + + /* + ** Fix all vars in the clause + ** to point to the right varno and varattno in parentpath + */ + xfunc_fixvars(get_clause(newinfo), newrel, whichchild); + + /* add clause to parentpath, and fix up its cost. */ + set_locclauseinfo(parentpath, + lispCons((LispValue)newinfo, + (LispValue)get_locclauseinfo(parentpath))); + /* put new childpath into the path tree */ + if (whichchild == INNER) { + set_innerjoinpath(parentpath, (pathPtr)newkid); + }else { + set_outerjoinpath(parentpath, (pathPtr)newkid); + } + + /* + ** recompute parentpath cost from scratch -- the cost + ** of the join method has changed + */ + cost = xfunc_total_path_cost(parentpath); + set_path_cost(parentpath, cost); + + return(newinfo); +} + +/* + ** calculate (selectivity-1)/cost. + */ +Cost xfunc_rank(Query *queryInfo,LispValue clause) +{ + Cost selec = compute_clause_selec(queryInfo, clause, LispNil); + Cost cost = xfunc_expense(queryInfo,clause); + + if (cost == 0) + if (selec > 1) return(MAXFLOAT); + else return(-(MAXFLOAT)); + return((selec - 1)/cost); +} + +/* + ** Find the "global" expense of a clause; i.e. the local expense divided + ** by the cardinalities of all the base relations of the query that are *not* + ** referenced in the clause. + */ +Cost xfunc_expense(Query* queryInfo, clause) + LispValue clause; +{ + Cost cost = xfunc_local_expense(clause); + + if (cost) + { + Count card = xfunc_card_unreferenced(queryInfo, clause, LispNil); + if (card) + cost /= card; + } + + return(cost); +} + +/* + ** xfunc_join_expense -- + ** Find global expense of a join clause + */ +Cost xfunc_join_expense(Query *queryInfo, JoinPath path, int whichchild) +{ + LispValue primjoinclause = xfunc_primary_join(path); + + /* + ** the second argument to xfunc_card_unreferenced reflects all the + ** relations involved in the join clause, i.e. all the relids in the Rel + ** of the join clause + */ + Count card = 0; + Cost cost = xfunc_expense_per_tuple(path, whichchild); + + card = xfunc_card_unreferenced(queryInfo, + primjoinclause, + get_relids(get_parent(path))); + if (primjoinclause) + cost += xfunc_local_expense(primjoinclause); + + if (card) cost /= card; + + return(cost); +} + +/* + ** Recursively find the per-tuple expense of a clause. See + ** xfunc_func_expense for more discussion. + */ +Cost xfunc_local_expense(LispValue clause) +{ + Cost cost = 0; /* running expense */ + LispValue tmpclause; + + /* First handle the base case */ + if (IsA(clause,Const) || IsA(clause,Var) || IsA(clause,Param)) + return(0); + /* now other stuff */ + else if (IsA(clause,Iter)) + /* Too low. Should multiply by the expected number of iterations. */ + return(xfunc_local_expense(get_iterexpr((Iter)clause))); + else if (IsA(clause,ArrayRef)) + return(xfunc_local_expense(get_refexpr((ArrayRef)clause))); + else if (fast_is_clause(clause)) + return(xfunc_func_expense((LispValue)get_op(clause), + (LispValue)get_opargs(clause))); + else if (fast_is_funcclause(clause)) + return(xfunc_func_expense((LispValue)get_function(clause), + (LispValue)get_funcargs(clause))); + else if (fast_not_clause(clause)) + return(xfunc_local_expense(lsecond(clause))); + else if (fast_or_clause(clause)) { + /* find cost of evaluating each disjunct */ + for (tmpclause = lnext(clause); tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + cost += xfunc_local_expense(lfirst(tmpclause)); + return(cost); + }else { + elog(WARN, "Clause node of undetermined type"); + return(-1); + } +} + +/* + ** xfunc_func_expense -- + ** given a Func or Oper and its args, find its expense. + ** Note: in Stonebraker's SIGMOD '91 paper, he uses a more complicated metric + ** than the one here. We can ignore the expected number of tuples for + ** our calculations; we just need the per-tuple expense. But he also + ** proposes components to take into account the costs of accessing disk and + ** archive. We didn't adopt that scheme here; eventually the vacuum + ** cleaner should be able to tell us what percentage of bytes to find on + ** which storage level, and that should be multiplied in appropriately + ** in the cost function below. Right now we don't model the cost of + ** accessing secondary or tertiary storage, since we don't have sufficient + ** stats to do it right. + */ +Cost xfunc_func_expense(LispValue node, LispValue args) +{ + HeapTuple tupl; /* the pg_proc tuple for each function */ + Form_pg_proc proc; /* a data structure to hold the pg_proc tuple */ + int width = 0; /* byte width of the field referenced by each clause */ + RegProcedure funcid; /* ID of function associate with node */ + Cost cost = 0; /* running expense */ + LispValue tmpclause; + LispValue operand; /* one operand of an operator */ + + if (IsA(node,Oper)) { + /* don't trust the opid in the Oper node. Use the opno. */ + if (!(funcid = get_opcode(get_opno((Oper)node)))) + elog(WARN, "Oper's function is undefined"); + }else { + funcid = get_funcid((Func)node); + } + + /* look up tuple in cache */ + tupl = SearchSysCacheTuple(PROOID, ObjectIdGetDatum(funcid),0,0,0); + if (!HeapTupleIsValid(tupl)) + elog(WARN, "Cache lookup failed for procedure %d", funcid); + proc = (Form_pg_proc) GETSTRUCT(tupl); + + /* + ** if it's a Postquel function, its cost is stored in the + ** associated plan. + */ + if (proc->prolang == SQLlanguageId) { + LispValue tmpplan; + List planlist; + + if (IsA(node,Oper) || get_func_planlist((Func)node) == LispNil) { + Oid *argOidVect; /* vector of argtypes */ + char *pq_src; /* text of PQ function */ + int nargs; /* num args to PQ function */ + QueryTreeList *queryTree_list; /* dummy variable */ + + /* + ** plan the function, storing it in the Func node for later + ** use by the executor. + */ + pq_src = (char *) textout(&(proc->prosrc)); + nargs = proc->pronargs; + if (nargs > 0) + argOidVect = proc->proargtypes; + planlist = (List)pg_plan(pq_src, argOidVect, nargs, + &parseTree_list, None); + if (IsA(node,Func)) + set_func_planlist((Func)node, planlist); + + }else {/* plan has been cached inside the Func node already */ + planlist = get_func_planlist((Func)node); + } + + /* + ** Return the sum of the costs of the plans (the PQ function + ** may have many queries in its body). + */ + foreach(tmpplan, planlist) + cost += get_cost((Plan)lfirst(tmpplan)); + return(cost); + }else { /* it's a C function */ + /* + ** find the cost of evaluating the function's arguments + ** and the width of the operands + */ + for (tmpclause = args; tmpclause != LispNil; + tmpclause = lnext(tmpclause)) { + + if ((operand = lfirst(tmpclause)) != LispNil) { + cost += xfunc_local_expense(operand); + width += xfunc_width(operand); + } + } + + /* + ** when stats become available, add in cost of accessing secondary + ** and tertiary storage here. + */ + return(cost + + (Cost)proc->propercall_cpu + + (Cost)proc->properbyte_cpu * (Cost)proc->probyte_pct/100.00 * + (Cost)width + /* + * Pct_of_obj_in_mem + DISK_COST * proc->probyte_pct/100.00 * width + * Pct_of_obj_on_disk + + ARCH_COST * proc->probyte_pct/100.00 * width + * Pct_of_obj_on_arch + */ + ); + } +} + +/* + ** xfunc_width -- + ** recursively find the width of a expression + */ + +int xfunc_width(LispValue clause) +{ + Relation rd; /* Relation Descriptor */ + HeapTuple tupl; /* structure to hold a cached tuple */ + TypeTupleForm type; /* structure to hold a type tuple */ + int retval = 0; + + if (IsA(clause,Const)) { + /* base case: width is the width of this constant */ + retval = get_constlen((Const) clause); + goto exit; + }else if (IsA(clause,ArrayRef)) { + /* base case: width is width of the refelem within the array */ + retval = get_refelemlength((ArrayRef)clause); + goto exit; + }else if (IsA(clause,Var)) { + /* base case: width is width of this attribute */ + tupl = SearchSysCacheTuple(TYPOID, + PointerGetDatum(get_vartype((Var)clause)), + 0,0,0); + if (!HeapTupleIsValid(tupl)) + elog(WARN, "Cache lookup failed for type %d", + get_vartype((Var)clause)); + type = (TypeTupleForm) GETSTRUCT(tupl); + if (get_varattno((Var)clause) == 0) { + /* clause is a tuple. Get its width */ + rd = heap_open(type->typrelid); + retval = xfunc_tuple_width(rd); + heap_close(rd); + }else { + /* attribute is a base type */ + retval = type->typlen; + } + goto exit; + }else if (IsA(clause,Param)) { + if (typeid_get_relid(get_paramtype((Param)clause))) { + /* Param node returns a tuple. Find its width */ + rd = heap_open(typeid_get_relid(get_paramtype((Param)clause))); + retval = xfunc_tuple_width(rd); + heap_close(rd); + }else if (get_param_tlist((Param)clause) != LispNil) { + /* Param node projects a complex type */ + Assert(length(get_param_tlist((Param)clause)) == 1); /* sanity */ + retval = + xfunc_width((LispValue) + get_expr(lfirst(get_param_tlist((Param)clause)))); + }else { + /* Param node returns a base type */ + retval = tlen(get_id_type(get_paramtype((Param)clause))); + } + goto exit; + }else if (IsA(clause,Iter)) { + /* + ** An Iter returns a setof things, so return the width of a single + ** thing. + ** Note: THIS MAY NOT WORK RIGHT WHEN AGGS GET FIXED, + ** SINCE AGG FUNCTIONS CHEW ON THE WHOLE SETOF THINGS!!!! + ** This whole Iter business is bogus, anyway. + */ + retval = xfunc_width(get_iterexpr((Iter)clause)); + goto exit; + }else if (fast_is_clause(clause)) { + /* + ** get function associated with this Oper, and treat this as + ** a Func + */ + tupl = SearchSysCacheTuple(OPROID, + ObjectIdGetDatum(get_opno((Oper)get_op(clause))), + 0,0,0); + if (!HeapTupleIsValid(tupl)) + elog(WARN, "Cache lookup failed for procedure %d", + get_opno((Oper)get_op(clause))); + return(xfunc_func_width + ((RegProcedure)(((OperatorTupleForm)(GETSTRUCT(tupl)))->oprcode), + (LispValue)get_opargs(clause))); + }else if (fast_is_funcclause(clause)) { + Func func = (Func)get_function(clause); + if (get_func_tlist(func) != LispNil) { + /* this function has a projection on it. Get the length + of the projected attribute */ + Assert(length(get_func_tlist(func)) == 1); /* sanity */ + retval = + xfunc_width((LispValue) + get_expr(lfirst(get_func_tlist(func)))); + goto exit; + }else { + return(xfunc_func_width((RegProcedure)get_funcid(func), + (LispValue)get_funcargs(clause))); + } + }else { + elog(WARN, "Clause node of undetermined type"); + return(-1); + } + + exit: + if (retval == -1) + retval = VARLEN_DEFAULT; + return(retval); +} + +/* + ** xfunc_card_unreferenced: + ** find all relations not referenced in clause, and multiply their + ** cardinalities. Ignore relation of cardinality 0. + ** User may pass in referenced list, if they know it (useful + ** for joins). + */ +static Count +xfunc_card_unreferenced(Query *queryInfo, + LispValue clause, Relid referenced) +{ + Relid unreferenced, allrelids = LispNil; + LispValue temp; + + /* find all relids of base relations referenced in query */ + foreach (temp,queryInfo->base_relation_list_) + { + Assert(lnext(get_relids((Rel)lfirst(temp))) == LispNil); + allrelids = lappend(allrelids, + lfirst(get_relids((Rel)lfirst(temp)))); + } + + /* find all relids referenced in query but not in clause */ + if (!referenced) + referenced = xfunc_find_references(clause); + unreferenced = set_difference(allrelids, referenced); + + return(xfunc_card_product(unreferenced)); +} + +/* + ** xfunc_card_product + ** multiple together cardinalities of a list relations. + */ +Count xfunc_card_product(Query *queryInfo, Relid relids) +{ + LispValue cinfonode; + LispValue temp; + Rel currel; + Cost tuples; + Count retval = 0; + + foreach(temp,relids) { + currel = get_rel(lfirst(temp)); + tuples = get_tuples(currel); + + if (tuples) { /* not of cardinality 0 */ + /* factor in the selectivity of all zero-cost clauses */ + foreach (cinfonode, get_clauseinfo(currel)) { + if (!xfunc_expense(queryInfo,get_clause((CInfo)lfirst(cinfonode)))) + tuples *= + compute_clause_selec(queryInfo, + get_clause((CInfo)lfirst(cinfonode)), + LispNil); + } + + if (retval == 0) retval = tuples; + else retval *= tuples; + } + } + if (retval == 0) retval = 1; /* saves caller from dividing by zero */ + return(retval); +} + + +/* + ** xfunc_find_references: + ** Traverse a clause and find all relids referenced in the clause. + */ +List xfunc_find_references(LispValue clause) +{ + List retval = (List)LispNil; + LispValue tmpclause; + + /* Base cases */ + if (IsA(clause,Var)) + return(lispCons(lfirst(get_varid((Var)clause)), LispNil)); + else if (IsA(clause,Const) || IsA(clause,Param)) + return((List)LispNil); + + /* recursion */ + else if (IsA(clause,Iter)) + /* Too low. Should multiply by the expected number of iterations. maybe */ + return(xfunc_find_references(get_iterexpr((Iter)clause))); + else if (IsA(clause,ArrayRef)) + return(xfunc_find_references(get_refexpr((ArrayRef)clause))); + else if (fast_is_clause(clause)) { + /* string together result of all operands of Oper */ + for (tmpclause = (LispValue)get_opargs(clause); tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + retval = nconc(retval, xfunc_find_references(lfirst(tmpclause))); + return(retval); + }else if (fast_is_funcclause(clause)) { + /* string together result of all args of Func */ + for (tmpclause = (LispValue)get_funcargs(clause); + tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + retval = nconc(retval, xfunc_find_references(lfirst(tmpclause))); + return(retval); + }else if (fast_not_clause(clause)) + return(xfunc_find_references(lsecond(clause))); + else if (fast_or_clause(clause)) { + /* string together result of all operands of OR */ + for (tmpclause = lnext(clause); tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + retval = nconc(retval, xfunc_find_references(lfirst(tmpclause))); + return(retval); + }else { + elog(WARN, "Clause node of undetermined type"); + return((List)LispNil); + } +} + +/* + ** xfunc_primary_join: + ** Find the primary join clause: for Hash and Merge Joins, this is the + ** min rank Hash or Merge clause, while for Nested Loop it's the + ** min rank pathclause + */ +LispValue xfunc_primary_join(JoinPath pathnode) +{ + LispValue joinclauselist = get_pathclauseinfo(pathnode); + CInfo mincinfo; + LispValue tmplist; + LispValue minclause = LispNil; + Cost minrank, tmprank; + + if (IsA(pathnode,MergePath)) + { + for(tmplist = get_path_mergeclauses((MergePath)pathnode), + minclause = lfirst(tmplist), + minrank = xfunc_rank(minclause); + tmplist != LispNil; + tmplist = lnext(tmplist)) + if ((tmprank = xfunc_rank(lfirst(tmplist))) + < minrank) + { + minrank = tmprank; + minclause = lfirst(tmplist); + } + return(minclause); + } + else if (IsA(pathnode,HashPath)) + { + for(tmplist = get_path_hashclauses((HashPath)pathnode), + minclause = lfirst(tmplist), + minrank = xfunc_rank(minclause); + tmplist != LispNil; + tmplist = lnext(tmplist)) + if ((tmprank = xfunc_rank(lfirst(tmplist))) + < minrank) + { + minrank = tmprank; + minclause = lfirst(tmplist); + } + return(minclause); + } + + /* if we drop through, it's nested loop join */ + if (joinclauselist == LispNil) + return(LispNil); + + for(tmplist = joinclauselist, mincinfo = (CInfo) lfirst(joinclauselist), + minrank = xfunc_rank(get_clause((CInfo) lfirst(tmplist))); + tmplist != LispNil; + tmplist = lnext(tmplist)) + if ((tmprank = xfunc_rank(get_clause((CInfo) lfirst(tmplist)))) + < minrank) + { + minrank = tmprank; + mincinfo = (CInfo) lfirst(tmplist); + } + return((LispValue)get_clause(mincinfo)); +} + +/* + ** xfunc_get_path_cost + ** get the expensive function costs of the path + */ +Cost xfunc_get_path_cost(Query *queryInfo, Path pathnode) +{ + Cost cost = 0; + LispValue tmplist; + Cost selec = 1.0; + + /* + ** first add in the expensive local function costs. + ** We ensure that the clauses are sorted by rank, so that we + ** know (via selectivities) the number of tuples that will be checked + ** by each function. If we're not doing any optimization of expensive + ** functions, we don't sort. + */ + if (XfuncMode != XFUNC_OFF) + set_locclauseinfo(pathnode, lisp_qsort(get_locclauseinfo(pathnode), + xfunc_cinfo_compare)); + for(tmplist = get_locclauseinfo(pathnode), selec = 1.0; + tmplist != LispNil; + tmplist = lnext(tmplist)) + { + cost += (Cost)(xfunc_local_expense(get_clause((CInfo)lfirst(tmplist))) + * (Cost)get_tuples(get_parent(pathnode)) * selec); + selec *= compute_clause_selec(queryInfo, + get_clause((CInfo)lfirst(tmplist)), + LispNil); + } + + /* + ** Now add in any node-specific expensive function costs. + ** Again, we must ensure that the clauses are sorted by rank. + */ + if (IsA(pathnode,JoinPath)) + { + if (XfuncMode != XFUNC_OFF) + set_pathclauseinfo((JoinPath)pathnode, lisp_qsort + (get_pathclauseinfo((JoinPath)pathnode), + xfunc_cinfo_compare)); + for(tmplist = get_pathclauseinfo((JoinPath)pathnode), selec = 1.0; + tmplist != LispNil; + tmplist = lnext(tmplist)) + { + cost += (Cost)(xfunc_local_expense(get_clause((CInfo)lfirst(tmplist))) + * (Cost)get_tuples(get_parent(pathnode)) * selec); + selec *= compute_clause_selec(queryInfo, + get_clause((CInfo)lfirst(tmplist)), + LispNil); + } + } + if (IsA(pathnode,HashPath)) + { + if (XfuncMode != XFUNC_OFF) + set_path_hashclauses + ((HashPath)pathnode, + lisp_qsort(get_path_hashclauses((HashPath)pathnode), + xfunc_clause_compare)); + for(tmplist = get_path_hashclauses((HashPath)pathnode), selec = 1.0; + tmplist != LispNil; + tmplist = lnext(tmplist)) + { + cost += (Cost)(xfunc_local_expense(lfirst(tmplist)) + * (Cost)get_tuples(get_parent(pathnode)) * selec); + selec *= compute_clause_selec(queryInfo, + lfirst(tmplist), LispNil); + } + } + if (IsA(pathnode,MergePath)) + { + if (XfuncMode != XFUNC_OFF) + set_path_mergeclauses + ((MergePath)pathnode, + lisp_qsort(get_path_mergeclauses((MergePath)pathnode), + xfunc_clause_compare)); + for(tmplist = get_path_mergeclauses((MergePath)pathnode), selec = 1.0; + tmplist != LispNil; + tmplist = lnext(tmplist)) + { + cost += (Cost)(xfunc_local_expense(lfirst(tmplist)) + * (Cost)get_tuples(get_parent(pathnode)) * selec); + selec *= compute_clause_selec(queryInfo, + lfirst(tmplist), LispNil); + } + } + Assert(cost >= 0); + return(cost); +} + +/* + ** Recalculate the cost of a path node. This includes the basic cost of the + ** node, as well as the cost of its expensive functions. + ** We need to do this to the parent after pulling a clause from a child into a + ** parent. Thus we should only be calling this function on JoinPaths. + */ +Cost xfunc_total_path_cost(JoinPath pathnode) +{ + Cost cost = xfunc_get_path_cost((Path)pathnode); + + Assert(IsA(pathnode,JoinPath)); + if (IsA(pathnode,MergePath)) + { + MergePath mrgnode = (MergePath)pathnode; + cost += cost_mergesort(get_path_cost((Path)get_outerjoinpath(mrgnode)), + get_path_cost((Path)get_innerjoinpath(mrgnode)), + get_outersortkeys(mrgnode), + get_innersortkeys(mrgnode), + get_tuples(get_parent((Path)get_outerjoinpath + (mrgnode))), + get_tuples(get_parent((Path)get_innerjoinpath + (mrgnode))), + get_width(get_parent((Path)get_outerjoinpath + (mrgnode))), + get_width(get_parent((Path)get_innerjoinpath + (mrgnode)))); + Assert(cost >= 0); + return(cost); + } + else if (IsA(pathnode,HashPath)) + { + HashPath hashnode = (HashPath)pathnode; + cost += cost_hashjoin(get_path_cost((Path)get_outerjoinpath(hashnode)), + get_path_cost((Path)get_innerjoinpath(hashnode)), + get_outerhashkeys(hashnode), + get_innerhashkeys(hashnode), + get_tuples(get_parent((Path)get_outerjoinpath + (hashnode))), + get_tuples(get_parent((Path)get_innerjoinpath + (hashnode))), + get_width(get_parent((Path)get_outerjoinpath + (hashnode))), + get_width(get_parent((Path)get_innerjoinpath + (hashnode)))); + Assert (cost >= 0); + return(cost); + } + else /* Nested Loop Join */ + { + cost += cost_nestloop(get_path_cost((Path)get_outerjoinpath(pathnode)), + get_path_cost((Path)get_innerjoinpath(pathnode)), + get_tuples(get_parent((Path)get_outerjoinpath + (pathnode))), + get_tuples(get_parent((Path)get_innerjoinpath + (pathnode))), + get_pages(get_parent((Path)get_outerjoinpath + (pathnode))), + IsA(get_innerjoinpath(pathnode),IndexPath)); + Assert(cost >= 0); + return(cost); + } +} + + +/* + ** xfunc_expense_per_tuple -- + ** return the expense of the join *per-tuple* of the input relation. + ** The cost model here is that a join costs + ** k*card(outer)*card(inner) + l*card(outer) + m*card(inner) + n + ** + ** We treat the l and m terms by considering them to be like restrictions + ** constrained to be right under the join. Thus the cost per inner and + ** cost per outer of the join is different, reflecting these virtual nodes. + ** + ** The cost per tuple of outer is k + l/referenced(inner). Cost per tuple + ** of inner is k + m/referenced(outer). + ** The constants k, l, m and n depend on the join method. Measures here are + ** based on the costs in costsize.c, with fudging for HashJoin and Sorts to + ** make it fit our model (the 'q' in HashJoin results in a + ** card(outer)/card(inner) term, and sorting results in a log term. + + */ +Cost xfunc_expense_per_tuple(JoinPath joinnode, int whichchild) +{ + Rel outerrel = get_parent((Path)get_outerjoinpath(joinnode)); + Rel innerrel = get_parent((Path)get_innerjoinpath(joinnode)); + Count outerwidth = get_width(outerrel); + Count outers_per_page = ceil(BLCKSZ/(outerwidth + sizeof(HeapTupleData))); + + if (IsA(joinnode,HashPath)) + { + if (whichchild == INNER) + return((1 + _CPU_PAGE_WEIGHT_)*outers_per_page/NBuffers); + else + return(((1 + _CPU_PAGE_WEIGHT_)*outers_per_page/NBuffers) + + _CPU_PAGE_WEIGHT_ + / xfunc_card_product(get_relids(innerrel))); + } + else if (IsA(joinnode,MergePath)) + { + /* assumes sort exists, and costs one (I/O + CPU) per tuple */ + if (whichchild == INNER) + return((2*_CPU_PAGE_WEIGHT_ + 1) + / xfunc_card_product(get_relids(outerrel))); + else + return((2*_CPU_PAGE_WEIGHT_ + 1) + / xfunc_card_product(get_relids(innerrel))); + } + else /* nestloop */ + { + Assert(IsA(joinnode,JoinPath)); + return(_CPU_PAGE_WEIGHT_); + } +} + +/* + ** xfunc_fixvars -- + ** After pulling up a clause, we must walk its expression tree, fixing Var + ** nodes to point to the correct varno (either INNER or OUTER, depending + ** on which child the clause was pulled from), and the right varattno in the + ** target list of the child's former relation. If the target list of the + ** child Rel does not contain the attribute we need, we add it. + */ +void xfunc_fixvars(LispValue clause, /* clause being pulled up */ + Rel rel, /* rel it's being pulled from */ + int varno) /* whether rel is INNER or OUTER of join */ +{ + LispValue tmpclause; /* temporary variable */ + TargetEntry *tle; /* tlist member corresponding to var */ + + + if (IsA(clause,Const) || IsA(clause,Param)) return; + else if (IsA(clause,Var)) + { + /* here's the meat */ + tle = tlistentry_member((Var)clause, get_targetlist(rel)); + if (tle == LispNil) + { + /* + ** The attribute we need is not in the target list, + ** so we have to add it. + ** + */ + add_tl_element(rel, (Var)clause); + tle = tlistentry_member((Var)clause, get_targetlist(rel)); + } + set_varno(((Var)clause), varno); + set_varattno(((Var)clause), get_resno(get_resdom(get_entry(tle)))); + } + else if (IsA(clause,Iter)) + xfunc_fixvars(get_iterexpr((Iter)clause), rel, varno); + else if (fast_is_clause(clause)) + { + xfunc_fixvars(lfirst(lnext(clause)), rel, varno); + xfunc_fixvars(lfirst(lnext(lnext(clause))), rel, varno); + } + else if (fast_is_funcclause(clause)) + for (tmpclause = lnext(clause); tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + xfunc_fixvars(lfirst(tmpclause), rel, varno); + else if (fast_not_clause(clause)) + xfunc_fixvars(lsecond(clause), rel, varno); + else if (fast_or_clause(clause)) + for (tmpclause = lnext(clause); tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + xfunc_fixvars(lfirst(tmpclause), rel, varno); + else + { + elog(WARN, "Clause node of undetermined type"); + } +} + + +/* + ** Comparison function for lisp_qsort() on a list of CInfo's. + ** arg1 and arg2 should really be of type (CInfo *). + */ +int xfunc_cinfo_compare(void *arg1, void *arg2) +{ + CInfo info1 = *(CInfo *) arg1; + CInfo info2 = *(CInfo *) arg2; + + LispValue clause1 = (LispValue) get_clause(info1), + clause2 = (LispValue) get_clause(info2); + + return(xfunc_clause_compare((void *) &clause1, (void *) &clause2)); +} + +/* + ** xfunc_clause_compare: comparison function for lisp_qsort() that compares two + ** clauses based on expense/(1 - selectivity) + ** arg1 and arg2 are really pointers to clauses. + */ +int xfunc_clause_compare(void *arg1, void *arg2) +{ + LispValue clause1 = *(LispValue *) arg1; + LispValue clause2 = *(LispValue *) arg2; + Cost rank1, /* total xfunc rank of clause1 */ + rank2; /* total xfunc rank of clause2 */ + + rank1 = xfunc_rank(clause1); + rank2 = xfunc_rank(clause2); + + if ( rank1 < rank2) + return(-1); + else if (rank1 == rank2) + return(0); + else return(1); +} + +/* + ** xfunc_disjunct_sort -- + ** given a list of clauses, for each clause sort the disjuncts by cost + ** (this assumes the predicates have been converted to Conjunctive NF) + ** Modifies the clause list! + */ +void xfunc_disjunct_sort(LispValue clause_list) +{ + LispValue temp; + + foreach(temp, clause_list) + if(or_clause(lfirst(temp))) + lnext(lfirst(temp)) = + lisp_qsort(lnext(lfirst(temp)), xfunc_disjunct_compare); +} + + +/* + ** xfunc_disjunct_compare: comparison function for qsort() that compares two + ** disjuncts based on cost/selec. + ** arg1 and arg2 are really pointers to disjuncts + */ +int xfunc_disjunct_compare(Query* queryInfo, void *arg1, void *arg2) +{ + LispValue disjunct1 = *(LispValue *) arg1; + LispValue disjunct2 = *(LispValue *) arg2; + Cost cost1, /* total cost of disjunct1 */ + cost2, /* total cost of disjunct2 */ + selec1, + selec2; + Cost rank1, rank2; + + cost1 = xfunc_expense(queryInfo, disjunct1); + cost2 = xfunc_expense(queryInfo, disjunct2); + selec1 = compute_clause_selec(queryInfo, + disjunct1, LispNil); + selec2 = compute_clause_selec(queryInfo, + disjunct2, LispNil); + + if (selec1 == 0) + rank1 = MAXFLOAT; + else if (cost1 == 0) + rank1 = 0; + else + rank1 = cost1/selec1; + + if (selec2 == 0) + rank2 = MAXFLOAT; + else if (cost2 == 0) + rank2 = 0; + else + rank2 = cost2/selec2; + + if ( rank1 < rank2) + return(-1); + else if (rank1 == rank2) + return(0); + else return(1); +} + +/* ------------------------ UTILITY FUNCTIONS ------------------------------- */ +/* + ** xfunc_func_width -- + ** Given a function OID and operands, find the width of the return value. + */ +int xfunc_func_width(RegProcedure funcid, LispValue args) +{ + Relation rd; /* Relation Descriptor */ + HeapTuple tupl; /* structure to hold a cached tuple */ + Form_pg_proc proc; /* structure to hold the pg_proc tuple */ + TypeTupleForm type; /* structure to hold the pg_type tuple */ + LispValue tmpclause; + int retval; + + /* lookup function and find its return type */ + Assert(RegProcedureIsValid(funcid)); + tupl = SearchSysCacheTuple(PROOID, ObjectIdGetDatum(funcid), 0,0,0); + if (!HeapTupleIsValid(tupl)) + elog(WARN, "Cache lookup failed for procedure %d", funcid); + proc = (Form_pg_proc) GETSTRUCT(tupl); + + /* if function returns a tuple, get the width of that */ + if (typeid_get_relid(proc->prorettype)) + { + rd = heap_open(typeid_get_relid(proc->prorettype)); + retval = xfunc_tuple_width(rd); + heap_close(rd); + goto exit; + } + else /* function returns a base type */ + { + tupl = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(proc->prorettype), + 0,0,0); + if (!HeapTupleIsValid(tupl)) + elog(WARN, "Cache lookup failed for type %d", proc->prorettype); + type = (TypeTupleForm) GETSTRUCT(tupl); + /* if the type length is known, return that */ + if (type->typlen != -1) + { + retval = type->typlen; + goto exit; + } + else /* estimate the return size */ + { + /* find width of the function's arguments */ + for (tmpclause = args; tmpclause != LispNil; + tmpclause = lnext(tmpclause)) + retval += xfunc_width(lfirst(tmpclause)); + /* multiply by outin_ratio */ + retval = (int)(proc->prooutin_ratio/100.0 * retval); + goto exit; + } + } + exit: + return(retval); +} + +/* + ** xfunc_tuple_width -- + ** Return the sum of the lengths of all the attributes of a given relation + */ +int xfunc_tuple_width(Relation rd) +{ + int i; + int retval = 0; + TupleDesc tdesc = RelationGetTupleDescriptor(rd); + + for (i = 0; i < tdesc->natts; i++) + { + if (tdesc->attrs[i]->attlen != -1) + retval += tdesc->attrs[i]->attlen; + else retval += VARLEN_DEFAULT; + } + + return(retval); +} + +/* + ** xfunc_num_join_clauses -- + ** Find the number of join clauses associated with this join path + */ +int xfunc_num_join_clauses(JoinPath path) +{ + int num = length(get_pathclauseinfo(path)); + if (IsA(path,MergePath)) + return(num + length(get_path_mergeclauses((MergePath)path))); + else if (IsA(path,HashPath)) + return(num + length(get_path_hashclauses((HashPath)path))); + else return(num); +} + +/* + ** xfunc_LispRemove -- + ** Just like LispRemove, but it whines if the item to be removed ain't there + */ +LispValue xfunc_LispRemove(LispValue foo, List bar) +{ + LispValue temp = LispNil; + LispValue result = LispNil; + int sanity = false; + + for (temp = bar; !null(temp); temp = lnext(temp)) + if (! equal((Node)(foo),(Node)(lfirst(temp))) ) + { + result = lappend(result,lfirst(temp)); + } + else sanity = true; /* found a matching item to remove! */ + + if (!sanity) + elog(WARN, "xfunc_LispRemove: didn't find a match!"); + + return(result); +} + +#define Node_Copy(a, b, c, d) \ + if (NodeCopy((Node)((a)->d), (Node*)&((b)->d), c) != true) { \ + return false; \ + } + +/* + ** xfunc_copyrel -- + ** Just like _copyRel, but doesn't copy the paths + */ +bool xfunc_copyrel(Rel from, Rel *to) +{ + Rel newnode; + Pointer (*alloc)() = palloc; + + /* COPY_CHECKARGS() */ + if (to == NULL) + { + return false; + } + + /* COPY_CHECKNULL() */ + if (from == NULL) + { + (*to) = NULL; + return true; + } + + /* COPY_NEW(c) */ + newnode = (Rel)(*alloc)(classSize(Rel)); + if (newnode == NULL) + { + return false; + } + + /* ---------------- + * copy node superclass fields + * ---------------- + */ + CopyNodeFields((Node)from, (Node)newnode, alloc); + + /* ---------------- + * copy remainder of node + * ---------------- + */ + Node_Copy(from, newnode, alloc, relids); + + newnode->indexed = from->indexed; + newnode->pages = from->pages; + newnode->tuples = from->tuples; + newnode->size = from->size; + newnode->width = from->width; + + Node_Copy(from, newnode, alloc, targetlist); + /* No!!!! Node_Copy(from, newnode, alloc, pathlist); + Node_Copy(from, newnode, alloc, unorderedpath); + Node_Copy(from, newnode, alloc, cheapestpath); */ +#if 0 /* can't use Node_copy now. 2/95 -ay */ + Node_Copy(from, newnode, alloc, classlist); + Node_Copy(from, newnode, alloc, indexkeys); + Node_Copy(from, newnode, alloc, ordering); +#endif + Node_Copy(from, newnode, alloc, clauseinfo); + Node_Copy(from, newnode, alloc, joininfo); + Node_Copy(from, newnode, alloc, innerjoin); + Node_Copy(from, newnode, alloc, superrels); + + (*to) = newnode; + return true; +} diff --git a/src/backend/optimizer/pathnode.h b/src/backend/optimizer/pathnode.h new file mode 100644 index 00000000000..0617600d4eb --- /dev/null +++ b/src/backend/optimizer/pathnode.h @@ -0,0 +1,50 @@ +/*------------------------------------------------------------------------- + * + * pathnode.h-- + * prototypes for pathnode.c, indexnode.c, relnode.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pathnode.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PATHNODE_H +#define PATHNODE_H + +/* + * prototypes for pathnode.c + */ +extern bool path_is_cheaper(Path *path1, Path *path2); +extern Path *set_cheapest(Rel *parent_rel, List *pathlist); +extern List *add_pathlist(Rel *parent_rel, List *unique_paths, + List *new_paths); +extern Path *create_seqscan_path(Rel *rel); +extern IndexPath *create_index_path(Query *root, Rel *rel, Rel *index, + List *restriction_clauses, bool is_join_scan); +extern JoinPath *create_nestloop_path(Rel *joinrel, Rel *outer_rel, + Path *outer_path, Path *inner_path, List *keys); +extern MergePath *create_mergesort_path(Rel *joinrel, int outersize, + int innersize, int outerwidth, int innerwidth, Path *outer_path, + Path *inner_path, List *keys, MergeOrder *order, + List *mergeclauses, List *outersortkeys, List *innersortkeys); + +extern HashPath *create_hashjoin_path(Rel *joinrel, int outersize, + int innersize, int outerwidth, int innerwidth, Path *outer_path, + Path *inner_path, List *keys, Oid operator, List *hashclauses, + List *outerkeys, List *innerkeys); + +/* + * prototypes for rel.c + */ +extern Rel *rel_member(List *relid, List *rels); +extern Rel *get_base_rel(Query* root, int relid); +extern Rel *get_join_rel(Query* root, List *relid); + +/* + * prototypes for indexnode.h + */ +extern List *find_relation_indices(Query *root,Rel *rel); + +#endif /* PATHNODE_H */ diff --git a/src/backend/optimizer/paths.h b/src/backend/optimizer/paths.h new file mode 100644 index 00000000000..62468041cfd --- /dev/null +++ b/src/backend/optimizer/paths.h @@ -0,0 +1,89 @@ +/*------------------------------------------------------------------------- + * + * paths.h-- + * prototypes for various files in optimizer/paths (were separate + * header files + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: paths.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PATHS_H +#define PATHS_H + +/* + * allpaths.h + */ +extern List *find_paths(Query *root, List *rels); + +/* + * indxpath.h + * routines to generate index paths + */ +extern List *find_index_paths(Query *root, Rel *rel, List *indices, + List *clauseinfo_list, + List *joininfo_list); + +/* + * joinpath.h + * routines to create join paths + */ +extern void find_all_join_paths(Query *root, List *joinrels); + + +/* + * orindxpath.h + */ +extern List *create_or_index_paths(Query *root, Rel *rel, List *clauses); + +/* + * hashutils.h + * routines to deal with hash keys and clauses + */ +extern List *group_clauses_by_hashop(List *clauseinfo_list, + int inner_relid); + +/* + * joinutils.h + * generic join method key/clause routines + */ +extern List *match_pathkeys_joinkeys(List *pathkeys, + List *joinkeys, List *joinclauses, int which_subkey, + List **matchedJoinClausesPtr); +extern List *extract_path_keys(List *joinkeys, List *tlist, + int which_subkey); +extern Path *match_paths_joinkeys(List *joinkeys, PathOrder *ordering, + List *paths, int which_subkey); +extern List *new_join_pathkeys(List *outer_pathkeys, + List *join_rel_tlist, List *joinclauses); + +/* + * mergeutils.h + * routines to deal with merge keys and clauses + */ +extern List *group_clauses_by_order(List *clauseinfo_list, + int inner_relid); +extern MInfo *match_order_mergeinfo(PathOrder *ordering, + List *mergeinfo_list); + +/* + * joinrels.h + * routines to determine which relations to join + */ +extern List *find_join_rels(Query *root, List *outer_rels); +extern void add_new_joininfos(Query *root, List *joinrels, List *outerrels); +extern List *final_join_rels(List *join_rel_list); + +/* + * prototypes for path/prune.c + */ +extern List *prune_joinrels(List *rel_list); +extern void prune_rel_paths(List *rel_list); +extern Path *prune_rel_path(Rel *rel, Path *unorderedpath); +extern List *merge_joinrels(List *rel_list1, List *rel_list2); +extern List *prune_oldrels(List *old_rels); + +#endif /* PATHS_H */ diff --git a/src/backend/optimizer/plan/Makefile.inc b/src/backend/optimizer/plan/Makefile.inc new file mode 100644 index 00000000000..eccd412e9ff --- /dev/null +++ b/src/backend/optimizer/plan/Makefile.inc @@ -0,0 +1,15 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for optimizer/plan +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/optimizer/plan/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= createplan.c initsplan.c planmain.c planner.c \ + setrefs.c diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c new file mode 100644 index 00000000000..70a5e6461bd --- /dev/null +++ b/src/backend/optimizer/plan/createplan.c @@ -0,0 +1,1097 @@ +/*------------------------------------------------------------------------- + * + * createplan.c-- + * Routines to create the desired plan for processing a query + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "c.h" + +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" +#include "nodes/nodeFuncs.h" + +#include "nodes/makefuncs.h" + +#include "utils/elog.h" +#include "utils/lsyscache.h" +#include "utils/palloc.h" +#include "utils/builtins.h" + +#include "parser/parse_query.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/planmain.h" +#include "optimizer/tlist.h" +#include "optimizer/planner.h" +#include "optimizer/xfunc.h" +#include "optimizer/internal.h" + + +#define TEMP_SORT 1 +#define TEMP_MATERIAL 2 + +static List *switch_outer(List *clauses); +static Scan *create_scan_node(Path *best_path, List *tlist); +static Join *create_join_node(JoinPath *best_path, List *tlist); +static SeqScan *create_seqscan_node(Path *best_path, List *tlist, + List *scan_clauses); +static IndexScan *create_indexscan_node(IndexPath *best_path, List *tlist, + List *scan_clauses); +static NestLoop *create_nestloop_node(JoinPath *best_path, List *tlist, + List *clauses, Plan *outer_node, List *outer_tlist, + Plan *inner_node, List *inner_tlist); +static MergeJoin *create_mergejoin_node(MergePath *best_path, List *tlist, + List *clauses, Plan *outer_node, List *outer_tlist, + Plan *inner_node, List *inner_tlist); +static HashJoin *create_hashjoin_node(HashPath *best_path, List *tlist, + List *clauses, Plan *outer_node, List *outer_tlist, + Plan *inner_node, List *inner_tlist); +static Node *fix_indxqual_references(Node *clause, Path *index_path); +static Temp *make_temp(List *tlist, List *keys, Oid *operators, + Plan *plan_node, int temptype); +static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, + List *indxid, List *indxqual); +static NestLoop *make_nestloop(List *qptlist, List *qpqual, Plan *lefttree, + Plan *righttree); +static HashJoin *make_hashjoin(List *tlist, List *qpqual, + List *hashclauses, Plan *lefttree, Plan *righttree); +static Hash *make_hash(List *tlist, Var *hashkey, Plan *lefttree); +static MergeJoin *make_mergesort(List * tlist, List *qpqual, + List *mergeclauses, Oid opcode, Oid *rightorder, + Oid *leftorder, Plan *righttree, Plan *lefttree); +static Material *make_material(List *tlist, Oid tempid, Plan *lefttree, + int keycount); + +/* + * 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 + * every pathnode found: + * (1) Create a corresponding plan node containing appropriate id, + * target list, and qualification information. + * (2) Modify ALL clauses so that attributes are referenced using + * relative values. + * (3) Target lists are not modified, but will be in another routine. + * + * best-path is the best access path + * + * Returns the optimal(?) access plan. + */ +Plan * +create_plan(Path *best_path) +{ + List *tlist; + Plan *plan_node = (Plan*)NULL; + Rel *parent_rel; + int size; + int width; + int pages; + int tuples; + + parent_rel = best_path->parent; + tlist = get_actual_tlist(parent_rel->targetlist); + size = parent_rel->size; + width = parent_rel->width; + pages = parent_rel->pages; + tuples = parent_rel->tuples; + + switch(best_path->pathtype) { + case T_IndexScan : + case T_SeqScan : + plan_node = (Plan*)create_scan_node(best_path, tlist); + break; + case T_HashJoin : + case T_MergeJoin : + case T_NestLoop: + plan_node = (Plan*)create_join_node((JoinPath*)best_path, tlist); + break; + default: + /* do nothing */ + break; + } + + plan_node->plan_size = size; + plan_node->plan_width = width; + if (pages == 0) pages = 1; + plan_node->plan_tupperpage = tuples/pages; + +#if 0 /* fix xfunc */ + /* sort clauses by cost/(1-selectivity) -- JMH 2/26/92 */ + if (XfuncMode != XFUNC_OFF) + { + set_qpqual((Plan) plan_node, + lisp_qsort( get_qpqual((Plan) plan_node), + xfunc_clause_compare)); + if (XfuncMode != XFUNC_NOR) + /* sort the disjuncts within each clause by cost -- JMH 3/4/92 */ + xfunc_disjunct_sort(plan_node->qpqual); + } +#endif + + return(plan_node); +} + +/* + * create_scan_node-- + * Create a scan path for the parent relation of 'best-path'. + * + * tlist is the targetlist for the base relation scanned by 'best-path' + * + * Returns the scan node. + */ +static Scan * +create_scan_node(Path *best_path, List *tlist) +{ + + Scan *node; + List *scan_clauses; + + /* + * Extract the relevant clauses from the parent relation and replace the + * operator OIDs with the corresponding regproc ids. + * + * now that local predicate clauses are copied into paths in + * find_rel_paths() and then (possibly) pulled up in xfunc_trypullup(), + * we get the relevant clauses from the path itself, not its parent + * relation. --- JMH, 6/15/92 + */ + scan_clauses = fix_opids(get_actual_clauses(best_path->locclauseinfo)); + + switch(best_path->pathtype) { + case T_SeqScan : + node = (Scan*)create_seqscan_node(best_path, tlist, scan_clauses); + break; + + case T_IndexScan: + node = (Scan*)create_indexscan_node((IndexPath*)best_path, + tlist, + scan_clauses); + break; + + default : + elog(WARN, "create_scan_node: unknown node type", + best_path->pathtype); + break; + } + + return node; +} + +/* + * create_join_node -- + * Create a join path for 'best-path' and(recursively) paths for its + * inner and outer paths. + * + * 'tlist' is the targetlist for the join relation corresponding to + * 'best-path' + * + * Returns the join node. + */ +static Join * +create_join_node(JoinPath *best_path, List *tlist) +{ + Plan *outer_node; + List *outer_tlist; + Plan *inner_node; + List *inner_tlist; + List *clauses; + Join *retval; + + outer_node = create_plan((Path*)best_path->outerjoinpath); + outer_tlist = outer_node->targetlist; + + inner_node = create_plan((Path*)best_path->innerjoinpath); + inner_tlist = inner_node->targetlist; + + clauses = get_actual_clauses(best_path->pathclauseinfo); + + switch(best_path->path.pathtype) { + case T_MergeJoin: + retval = (Join*)create_mergejoin_node((MergePath*)best_path, + tlist, + clauses, + outer_node, + outer_tlist, + inner_node, + inner_tlist); + break; + case T_HashJoin: + retval = (Join*)create_hashjoin_node((HashPath*)best_path, + tlist, + clauses, + outer_node, + outer_tlist, + inner_node, + inner_tlist); + break; + case T_NestLoop: + retval = (Join*)create_nestloop_node((JoinPath*)best_path, + tlist, + clauses, + outer_node, + outer_tlist, + inner_node, + inner_tlist); + break; + default: + /* do nothing */ + elog(WARN, "create_join_node: unknown node type", + best_path->path.pathtype); + } + +#if 0 + /* + ** Expensive function pullups may have pulled local predicates + ** into this path node. Put them in the qpqual of the plan node. + ** -- JMH, 6/15/92 + */ + if (get_locclauseinfo(best_path) != NIL) + set_qpqual((Plan)retval, + nconc(get_qpqual((Plan) retval), + fix_opids(get_actual_clauses + (get_locclauseinfo(best_path))))); +#endif + + return(retval); +} + +/***************************************************************************** + * + * BASE-RELATION SCAN METHODS + * + *****************************************************************************/ + + +/* + * create_seqscan_node-- + * Returns a seqscan node for the base relation scanned by 'best-path' + * with restriction clauses 'scan-clauses' and targetlist 'tlist'. + */ +static SeqScan * +create_seqscan_node(Path *best_path, List *tlist, List *scan_clauses) +{ + SeqScan *scan_node = (SeqScan*)NULL; + Index scan_relid = -1; + List *temp; + + temp = best_path->parent->relids; + if(temp == NULL) + elog(WARN,"scanrelid is empty"); + else + scan_relid = (Index)lfirst(temp); /* ??? who takes care of lnext? - ay */ + scan_node = make_seqscan(tlist, + scan_clauses, + scan_relid, + (Plan*)NULL); + + scan_node->plan.cost = best_path->path_cost; + + return(scan_node); +} + +/* + * create_indexscan_node-- + * Returns a indexscan node for the base relation scanned by 'best-path' + * with restriction clauses 'scan-clauses' and targetlist 'tlist'. + */ +static IndexScan * +create_indexscan_node(IndexPath *best_path, + List *tlist, + List *scan_clauses) +{ + /* + * Extract the(first if conjunct, only if disjunct) clause from the + * clauseinfo list. + */ + Expr *index_clause = (Expr*)NULL; + List *indxqual = NIL; + List *qpqual = NIL; + List *fixed_indxqual = NIL; + IndexScan *scan_node = (IndexScan*)NULL; + + + /* + * If an 'or' clause is to be used with this index, the indxqual + * field will contain a list of the 'or' clause arguments, e.g., the + * clause(OR a b c) will generate: ((a) (b) (c)). Otherwise, the + * indxqual will simply contain one conjunctive qualification: ((a)). + */ + if (best_path->indexqual != NULL) + /* added call to fix_opids, JMH 6/23/92 */ + index_clause = (Expr*) + lfirst(fix_opids(get_actual_clauses(best_path->indexqual))); + + if (or_clause((Node*)index_clause)) { + List *temp = NIL; + + foreach(temp, index_clause->args) + indxqual = lappend(indxqual, lcons(lfirst(temp), NIL)); + } else { + indxqual = lcons(get_actual_clauses(best_path->indexqual), + NIL); + } + + /* + * The qpqual field contains all restrictions except the indxqual. + */ + if(or_clause((Node*)index_clause)) + qpqual = set_difference(scan_clauses, + lcons(index_clause,NIL)); + else + qpqual = set_difference(scan_clauses, lfirst(indxqual)); + + fixed_indxqual = + (List*)fix_indxqual_references((Node*)indxqual,(Path*)best_path); + + scan_node = + make_indexscan(tlist, + qpqual, + lfirsti(best_path->path.parent->relids), + best_path->indexid, + fixed_indxqual); + + scan_node->scan.plan.cost = best_path->path.path_cost; + + return(scan_node); +} + +/***************************************************************************** + * + * JOIN METHODS + * + *****************************************************************************/ + +static NestLoop * +create_nestloop_node(JoinPath *best_path, + List *tlist, + List *clauses, + Plan *outer_node, + List *outer_tlist, + Plan *inner_node, + List *inner_tlist) +{ + NestLoop *join_node = (NestLoop*)NULL; + + if (IsA(inner_node,IndexScan)) { + /* An index is being used to reduce the number of tuples scanned in + * the inner relation. + * There will never be more than one index used in the inner + * scan path, so we need only consider the first set of + * qualifications in indxqual. + */ + + List *inner_indxqual = lfirst(((IndexScan*)inner_node)->indxqual); + List *inner_qual = (inner_indxqual == NULL)? NULL:lfirst(inner_indxqual); + + /* If we have in fact found a join index qualification, remove these + * index clauses from the nestloop's join clauses and reset the + * inner(index) scan's qualification so that the var nodes refer to + * the proper outer join relation attributes. + */ + if (!(qual_clause_p((Node*)inner_qual))) { + List *new_inner_qual = NIL; + + clauses = set_difference(clauses,inner_indxqual); + new_inner_qual = + index_outerjoin_references(inner_indxqual, + outer_node->targetlist, + ((Scan*)inner_node)->scanrelid); + ((IndexScan*)inner_node)->indxqual = + lcons(new_inner_qual,NIL); + } + }else if (IsA_Join(inner_node)) { + inner_node = (Plan*)make_temp(inner_tlist, + NIL, + NULL, + inner_node, + TEMP_MATERIAL); + } + + join_node = make_nestloop(tlist, + join_references(clauses, + outer_tlist, + inner_tlist), + outer_node, + inner_node); + + join_node->join.cost = best_path->path.path_cost; + + return(join_node); +} + +static MergeJoin * +create_mergejoin_node(MergePath *best_path, + List *tlist, + List *clauses, + Plan *outer_node, + List *outer_tlist, + Plan *inner_node, + List *inner_tlist) +{ + List *qpqual, *mergeclauses; + RegProcedure opcode; + Oid *outer_order, *inner_order; + MergeJoin *join_node; + + + /* Separate the mergeclauses from the other join qualification + * clauses and set those clauses to contain references to lower + * attributes. + */ + qpqual = join_references(set_difference(clauses, + best_path->path_mergeclauses), + outer_tlist, + inner_tlist); + + /* Now set the references in the mergeclauses and rearrange them so + * that the outer variable is always on the left. + */ + mergeclauses = switch_outer(join_references(best_path->path_mergeclauses, + outer_tlist, + inner_tlist)); + + opcode = + get_opcode((best_path->jpath.path.p_ordering.ord.merge)->join_operator); + + outer_order = (Oid *)palloc(sizeof(Oid)*2); + outer_order[0] = + (best_path->jpath.path.p_ordering.ord.merge)->left_operator; + outer_order[1] = 0; + + inner_order = (Oid *)palloc(sizeof(Oid)*2); + inner_order[0] = + (best_path->jpath.path.p_ordering.ord.merge)->right_operator; + inner_order[1] = 0; + + /* Create explicit sort paths for the outer and inner join paths if + * necessary. The sort cost was already accounted for in the path. + */ + if (best_path->outersortkeys) { + Temp *sorted_outer_node = make_temp(outer_tlist, + best_path->outersortkeys, + outer_order, + outer_node, + TEMP_SORT); + sorted_outer_node->plan.cost = outer_node->cost; + outer_node = (Plan*)sorted_outer_node; + } + + if (best_path->innersortkeys) { + Temp *sorted_inner_node = make_temp(inner_tlist, + best_path->innersortkeys, + inner_order, + inner_node, + TEMP_SORT); + sorted_inner_node->plan.cost = outer_node->cost; + inner_node = (Plan*)sorted_inner_node; + } + + join_node = make_mergesort(tlist, + qpqual, + mergeclauses, + opcode, + inner_order, + outer_order, + inner_node, + outer_node); + + join_node->join.cost = best_path->jpath.path.path_cost; + + return(join_node); +} + +/* + * create_hashjoin_node-- XXX HASH + * + * Returns a new hashjoin node. + * + * XXX hash join ops are totally bogus -- how the hell do we choose + * these?? at runtime? what about a hash index? + */ +static HashJoin * +create_hashjoin_node(HashPath *best_path, + List *tlist, + List *clauses, + Plan *outer_node, + List *outer_tlist, + Plan *inner_node, + List *inner_tlist) +{ + List *qpqual; + List *hashclauses; + HashJoin *join_node; + Hash *hash_node; + Var *innerhashkey; + + /* Separate the hashclauses from the other join qualification clauses + * and set those clauses to contain references to lower attributes. + */ + qpqual = + join_references(set_difference(clauses, + best_path->path_hashclauses), + outer_tlist, + inner_tlist); + + /* Now set the references in the hashclauses and rearrange them so + * that the outer variable is always on the left. + */ + hashclauses = + switch_outer(join_references(best_path->path_hashclauses, + outer_tlist, + inner_tlist)); + + innerhashkey = get_rightop(lfirst(hashclauses)); + + hash_node = make_hash(inner_tlist, innerhashkey, inner_node); + join_node = make_hashjoin(tlist, + qpqual, + hashclauses, + outer_node, + (Plan*)hash_node); + join_node->join.cost = best_path->jpath.path.path_cost; + + return(join_node); +} + + +/***************************************************************************** + * + * SUPPORTING ROUTINES + * + *****************************************************************************/ + +static Node * +fix_indxqual_references(Node *clause, Path *index_path) +{ + Node *newclause; + + if (IsA(clause,Var)) { + if (lfirsti(index_path->parent->relids) == ((Var*)clause)->varno) { + int pos = 0; + int varatt = ((Var*)clause)->varattno; + int *indexkeys = index_path->parent->indexkeys; + + if (indexkeys) { + while (indexkeys[pos] != 0) { + if(varatt == indexkeys[pos]) { + break; + } + pos++; + } + } + newclause = copyObject((Node*)clause); + ((Var*)newclause)->varattno = pos + 1; + return (newclause); + } else { + return (clause); + } + } else if(IsA(clause,Const)) { + return(clause); + } else if(is_opclause(clause) && + is_funcclause((Node*)get_leftop((Expr*)clause)) && + ((Func*)((Expr*)get_leftop((Expr*)clause))->oper)->funcisindex){ + Var *newvar = + makeVar((Index)lfirst(index_path->parent->relids), + 1, /* func indices have one key */ + ((Func*)((Expr*)clause)->oper)->functype, + (Index)lfirst(index_path->parent->relids), + 0); + + return + ((Node*)make_opclause((Oper*)((Expr*)clause)->oper, + newvar, + get_rightop((Expr*)clause))); + + } else if (IsA(clause,Expr)) { + Expr *expr = (Expr*)clause; + List *new_subclauses = NIL; + Node *subclause = NULL; + List *i = NIL; + + foreach(i, expr->args) { + subclause = lfirst(i); + if(subclause) + new_subclauses = + lappend(new_subclauses, + fix_indxqual_references(subclause, + index_path)); + + } + + /* XXX new_subclauses should be a list of the form: + * ( (var var) (var const) ...) ? + */ + if(new_subclauses) { + return (Node*) + make_clause(expr->opType, expr->oper, new_subclauses); + } else { + return(clause); + } + } else { + List *oldclauses = (List*)clause; + List *new_subclauses = NIL; + Node *subclause = NULL; + List *i = NIL; + + foreach(i, oldclauses) { + subclause = lfirst(i); + if(subclause) + new_subclauses = + lappend(new_subclauses, + fix_indxqual_references(subclause, + index_path)); + + } + + /* XXX new_subclauses should be a list of the form: + * ( (var var) (var const) ...) ? + */ + if(new_subclauses) { + return (Node*)new_subclauses; + } else { + return (clause); + } + } +} + + +/* + * switch_outer-- + * Given a list of merge clauses, rearranges the elements within the + * clauses so the outer join variable is on the left and the inner is on + * the right. + * + * Returns the rearranged list ? + * + * XXX Shouldn't the operator be commuted?! + */ +static List * +switch_outer(List *clauses) +{ + List *t_list = NIL; + Expr *temp = NULL; + List *i = NIL; + Expr *clause; + + foreach(i,clauses) { + clause = lfirst(i); + if(var_is_outer(get_rightop(clause))) { + temp = make_clause(clause->opType, clause->oper, + lcons(get_rightop(clause), + lcons(get_leftop(clause), + NIL))); + t_list = lappend(t_list,temp); + } + else + t_list = lappend(t_list,clause); + } + return(t_list); +} + +/* + * set-temp-tlist-operators-- + * Sets the key and keyop fields of resdom nodes in a target list. + * + * 'tlist' is the target list + * 'pathkeys' is a list of N keys in the form((key1) (key2)...(keyn)), + * corresponding to vars in the target list that are to + * be sorted or hashed + * 'operators' is the corresponding list of N sort or hash operators + * 'keyno' is the first key number + * XXX - keyno ? doesn't exist - jeff + * + * Returns the modified target list. + */ +static List * +set_temp_tlist_operators(List *tlist, List *pathkeys, Oid *operators) +{ + Node *keys = NULL; + int keyno = 1; + Resdom *resdom = (Resdom*)NULL ; + List *i = NIL; + + foreach(i, pathkeys) { + keys = lfirst((List*)lfirst(i)); + resdom = tlist_member((Var*)keys, tlist); + if (resdom) { + + /* Order the resdom keys and replace the operator OID for each + * key with the regproc OID. + * + * XXX Note that the optimizer only generates merge joins + * with 1 operator (see create_mergejoin_node) - ay 2/95 + */ + resdom->reskey = keyno; + resdom->reskeyop = get_opcode(operators[0]); + } + keyno += 1; + } + return(tlist); +} + +/***************************************************************************** + * + * + *****************************************************************************/ + +/* + * make_temp-- + * Create plan nodes to sort or materialize relations into temporaries. The + * result returned for a sort will look like (SEQSCAN(SORT(plan-node))) + * or (SEQSCAN(MATERIAL(plan-node))) + * + * 'tlist' is the target list of the scan to be sorted or hashed + * 'keys' is the list of keys which the sort or hash will be done on + * 'operators' is the operators with which the sort or hash is to be done + * (a list of operator OIDs) + * 'plan-node' is the node which yields tuples for the sort + * 'temptype' indicates which operation(sort or hash) to perform + */ +static Temp * +make_temp(List *tlist, + List *keys, + Oid *operators, + Plan *plan_node, + int temptype) +{ + List *temp_tlist; + Temp *retval; + + /* Create a new target list for the temporary, with keys set. */ + temp_tlist = set_temp_tlist_operators(new_unsorted_tlist(tlist), + keys, + operators); + switch(temptype) { + case TEMP_SORT : + retval = (Temp*)make_seqscan(tlist, + NIL, + _TEMP_RELATION_ID_, + (Plan*)make_sort(temp_tlist, + _TEMP_RELATION_ID_, + plan_node, + length(keys))); + break; + + case TEMP_MATERIAL : + retval = (Temp*)make_seqscan(tlist, + NIL, + _TEMP_RELATION_ID_, + (Plan*)make_material(temp_tlist, + _TEMP_RELATION_ID_, + plan_node, + length(keys))); + break; + + default: + elog(WARN,"make_temp: unknown temp type %d", temptype); + + } + return(retval); +} + + +SeqScan * +make_seqscan(List *qptlist, + List *qpqual, + Index scanrelid, + Plan *lefttree) +{ + SeqScan *node = makeNode(SeqScan); + Plan *plan = &node->plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = lefttree; + plan->righttree = NULL; + node->scanrelid = scanrelid; + node->scanstate = (CommonScanState *)NULL; + + return(node); +} + +static IndexScan * +make_indexscan(List *qptlist, + List *qpqual, + Index scanrelid, + List *indxid, + List *indxqual) +{ + IndexScan *node = makeNode(IndexScan); + Plan *plan = &node->scan.plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->indxid = indxid; + node->indxqual = indxqual; + node->scan.scanstate = (CommonScanState *)NULL; + + return(node); +} + + +static NestLoop * +make_nestloop(List *qptlist, + List *qpqual, + Plan *lefttree, + Plan *righttree) +{ + NestLoop *node = makeNode(NestLoop); + Plan *plan = &node->join; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = lefttree; + plan->righttree = righttree; + node->nlstate = (NestLoopState*)NULL; + + return(node); +} + +static HashJoin * +make_hashjoin(List *tlist, + List *qpqual, + List *hashclauses, + Plan *lefttree, + Plan *righttree) +{ + HashJoin *node = makeNode(HashJoin); + Plan *plan = &node->join; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = qpqual; + plan->lefttree = lefttree; + plan->righttree = righttree; + node->hashclauses = hashclauses; + node->hashjointable = NULL; + node->hashjointablekey = 0; + node->hashjointablesize = 0; + node->hashdone = false; + + return(node); +} + +static Hash * +make_hash(List *tlist, Var *hashkey, Plan *lefttree) +{ + Hash *node = makeNode(Hash); + Plan *plan = &node->plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = NULL; + plan->lefttree = lefttree; + plan->righttree = NULL; + node->hashkey = hashkey; + node->hashtable = NULL; + node->hashtablekey = 0; + node->hashtablesize = 0; + + return(node); +} + +static MergeJoin * +make_mergesort(List *tlist, + List *qpqual, + List *mergeclauses, + Oid opcode, + Oid *rightorder, + Oid *leftorder, + Plan *righttree, + Plan *lefttree) +{ + MergeJoin *node = makeNode(MergeJoin); + Plan *plan = &node->join; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = qpqual; + plan->lefttree = lefttree; + plan->righttree = righttree; + node->mergeclauses = mergeclauses; + node->mergesortop = opcode; + node->mergerightorder = rightorder; + node->mergeleftorder = leftorder; + + return(node); +} + +Sort * +make_sort(List *tlist, Oid tempid, Plan *lefttree, int keycount) +{ + Sort *node = makeNode(Sort); + Plan *plan = &node->plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + node->tempid = tempid; + node->keycount = keycount; + + return(node); +} + +static Material * +make_material(List *tlist, + Oid tempid, + Plan *lefttree, + int keycount) +{ + Material *node = makeNode(Material); + Plan *plan = &node->plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + node->tempid = tempid; + node->keycount = keycount; + + return(node); +} + +Agg * +make_agg(List *tlist, int nagg, Aggreg **aggs) +{ + Agg *node = makeNode(Agg); + + node->plan.cost = 0.0; + node->plan.state = (EState*)NULL; + node->plan.qual = NULL; + node->plan.targetlist = tlist; + node->plan.lefttree = (Plan*)NULL; + node->plan.righttree = (Plan*)NULL; + node->numAgg = nagg; + node->aggs = aggs; + + return(node); +} + +Group * +make_group(List *tlist, + bool tuplePerGroup, + int ngrp, + AttrNumber *grpColIdx, + Sort *lefttree) +{ + Group *node = makeNode(Group); + + node->plan.cost = 0.0; + node->plan.state = (EState*)NULL; + node->plan.qual = NULL; + node->plan.targetlist = tlist; + node->plan.lefttree = (Plan*)lefttree; + node->plan.righttree = (Plan*)NULL; + node->tuplePerGroup = tuplePerGroup; + node->numCols = ngrp; + node->grpColIdx = grpColIdx; + + return(node); +} + +/* + * A unique node always has a SORT node in the lefttree. + * + * the uniqueAttr argument must be a null-terminated string, + * either the name of the attribute to select unique on + * or "*" + */ + +Unique * +make_unique(List *tlist, Plan *lefttree, char* uniqueAttr) +{ + Unique *node = makeNode(Unique); + Plan *plan = &node->plan; + + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + node->tempid = _TEMP_RELATION_ID_; + node->keycount = 0; + if (strcmp(uniqueAttr,"*") == 0) + node->uniqueAttr = NULL; + else + { + node->uniqueAttr=pstrdup(uniqueAttr); + } + return(node); +} + +List *generate_fjoin(List *tlist) +{ +#if 0 + List tlistP; + List newTlist = NIL; + List fjoinList = NIL; + int nIters = 0; + + /* + * Break the target list into elements with Iter nodes, + * and those without them. + */ + foreach(tlistP, tlist) { + List tlistElem; + + tlistElem = lfirst(tlistP); + if (IsA(lsecond(tlistElem),Iter)) { + nIters++; + fjoinList = lappend(fjoinList, tlistElem); + } else { + newTlist = lappend(newTlist, tlistElem); + } + } + + /* + * if we have an Iter node then we need to flatten. + */ + if (nIters > 0) { + List *inner; + List *tempList; + Fjoin *fjoinNode; + DatumPtr results = (DatumPtr)palloc(nIters*sizeof(Datum)); + BoolPtr alwaysDone = (BoolPtr)palloc(nIters*sizeof(bool)); + + inner = lfirst(fjoinList); + fjoinList = lnext(fjoinList); + fjoinNode = (Fjoin)MakeFjoin(false, + nIters, + inner, + results, + alwaysDone); + tempList = lcons(fjoinNode, NIL); + tempList = nconc(tempList, fjoinList); + newTlist = lappend(newTlist, tempList); + } + return newTlist; +#endif + return tlist; /* do nothing for now - ay 10/94 */ +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c new file mode 100644 index 00000000000..fc80402a050 --- /dev/null +++ b/src/backend/optimizer/plan/initsplan.c @@ -0,0 +1,391 @@ +/*------------------------------------------------------------------------- + * + * initsplan.c-- + * Target list, qualification, joininfo initialization routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/parsenodes.h" +#include "nodes/relation.h" +#include "nodes/makefuncs.h" + +#include "utils/lsyscache.h" +#include "utils/palloc.h" + +#include "optimizer/internal.h" +#include "optimizer/planmain.h" +#include "optimizer/joininfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" + +extern int Quiet; + +static void add_clause_to_rels(Query *root, List *clause); +static void add_join_clause_info_to_rels(Query *root, CInfo *clauseinfo, + List *join_relids); +static void add_vars_to_rels(Query *root, List *vars, List *join_relids); + +static MergeOrder *mergesortop(Expr *clause); +static Oid hashjoinop(Expr *clause); + + +/***************************************************************************** + * + * TARGET LISTS + * + *****************************************************************************/ + +/* + * initialize_rel_nodes-- + * Creates rel nodes for every relation mentioned in the target list + * 'tlist' (if a node hasn't already been created) and adds them to + * *query-relation-list*. Creates targetlist entries for each member of + * 'tlist' and adds them to the tlist field of the appropriate rel node. + * + * Returns nothing. + */ +void +initialize_base_rels_list(Query *root, List *tlist) +{ + List *tlist_vars = NIL; + List *l = NIL; + List *tvar = NIL; + + foreach (l, tlist) { + TargetEntry *entry = (TargetEntry *) lfirst(l); + + tlist_vars = append(tlist_vars, pull_var_clause(entry->expr)); + } + + /* now, the target list only contains Var nodes */ + foreach (tvar, tlist_vars) { + Var *var; + Index varno; + Rel *result; + + var = (Var*)lfirst(tvar); + varno = var->varno; + result = get_base_rel(root, varno); + + add_tl_element(result, var); + } +} + +/* + * add_missing_variables_to_base_rels - + * If we have range variable(s) in the FROM clause that does not appear + * in the target list nor qualifications, we add it to the base relation + * list. For instance, "select f.x from foo f, foo f2" is a join of f and + * f2. Note that if we have "select foo.x from foo f", it also gets turned + * into a join. + */ +void +add_missing_vars_to_base_rels(Query *root, List *tlist) +{ + List *l; + int varno; + + varno = 1; + foreach (l, root->rtable) { + RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); + List *relids; + Rel *result; + Var *var; + + relids = lconsi(varno, NIL); + if (rte->inFromCl && + !rel_member(relids, root->base_relation_list_)) { + + var = makeVar(varno, -2 , 26, varno, -2); + /* add it to base_relation_list_ */ + result = get_base_rel(root, varno); + add_tl_element(result, var); + } + pfree(relids); + varno++; + } + + return; +} + +/***************************************************************************** + * + * QUALIFICATIONS + * + *****************************************************************************/ + + + +/* + * initialize-qualification-- + * Initializes ClauseInfo and JoinInfo fields of relation entries for all + * relations appearing within clauses. Creates new relation entries if + * necessary, adding them to *query-relation-list*. + * + * Returns nothing of interest. + */ +void +initialize_base_rels_jinfo(Query *root, List *clauses) +{ + List *clause; + + foreach (clause, clauses) { + add_clause_to_rels(root, lfirst(clause)); + } + return; +} + +/* + * add-clause-to-rels-- + * Add clause information to either the 'ClauseInfo' or 'JoinInfo' field + * of a relation entry(depending on whether or not the clause is a join) + * by creating a new ClauseInfo node and setting appropriate fields + * within the nodes. + * + * Returns nothing of interest. + */ +static void +add_clause_to_rels(Query *root, List *clause) +{ + List *relids; + List *vars; + CInfo *clauseinfo = makeNode(CInfo); + + /* + * Retrieve all relids and vars contained within the clause. + */ + clause_relids_vars((Node*)clause, &relids, &vars); + + + clauseinfo->clause = (Expr*)clause; + clauseinfo->notclause = contains_not((Node*)clause); + clauseinfo->selectivity = 0; + clauseinfo->indexids = NIL; + clauseinfo->mergesortorder = (MergeOrder*)NULL; + clauseinfo->hashjoinoperator = (Oid)0; + + + + if(length(relids) == 1) { + Rel *rel = get_base_rel(root, lfirsti(relids)); + + /* + * There is only one relation participating in 'clause', + * so 'clause' must be a restriction clause. + */ + + /* the selectivity of the clause must be computed + regardless of whether it's a restriction or a join clause */ + if (is_funcclause((Node*)clause)) + { + /* + * XXX If we have a func clause set selectivity to 1/3, + * really need a true selectivity function. + */ + clauseinfo->selectivity = (Cost)0.3333333; + } + else + { + clauseinfo->selectivity = + compute_clause_selec(root, (Node*)clause, + NIL); + } + rel->clauseinfo = lcons(clauseinfo, + rel->clauseinfo); + } else { + /* + * 'clause' is a join clause, since there is more than one + * atom in the relid list. + */ + + if (is_funcclause((Node*)clause)) + { + /* + * XXX If we have a func clause set selectivity to 1/3, + * really need a true selectivity function. + */ + clauseinfo->selectivity = (Cost)0.3333333; + } + else + { + clauseinfo->selectivity = + compute_clause_selec(root, (Node*)clause, + NIL); + } + add_join_clause_info_to_rels(root, clauseinfo, relids); + add_vars_to_rels(root,vars, relids); + } +} + +/* + * add-join-clause-info-to-rels-- + * For every relation participating in a join clause, add 'clauseinfo' to + * the appropriate joininfo node(creating a new one and adding it to the + * appropriate rel node if necessary). + * + * 'clauseinfo' describes the join clause + * 'join-relids' is the list of relations participating in the join clause + * + * Returns nothing. + * + */ +static void +add_join_clause_info_to_rels(Query *root, CInfo *clauseinfo, List *join_relids) +{ + List *join_relid; + + foreach (join_relid, join_relids) { + JInfo *joininfo = + find_joininfo_node(get_base_rel(root, lfirsti(join_relid)), + intLispRemove((int)lfirst(join_relid), + join_relids)); + joininfo->jinfoclauseinfo = + lcons(clauseinfo, joininfo->jinfoclauseinfo); + + } +} + +/* + * add-vars-to-rels-- + * For each variable appearing in a clause, + * (1) If a targetlist entry for the variable is not already present in + * the appropriate relation's target list, add one. + * (2) If a targetlist entry is already present, but the var is part of a + * join clause, add the relids of the join relations to the JoinList + * entry of the targetlist entry. + * + * 'vars' is the list of var nodes + * 'join-relids' is the list of relids appearing in the join clause + * (if this is a join clause) + * + * Returns nothing. + */ +static void +add_vars_to_rels(Query *root, List *vars, List *join_relids) +{ + Var *var; + List *temp = NIL; + Rel *rel = (Rel*)NULL; + TargetEntry *tlistentry; + + foreach (temp, vars) { + var = (Var*)lfirst(temp); + rel = get_base_rel(root, var->varno); + tlistentry = tlistentry_member(var, rel->targetlist); + if(tlistentry==NULL) + /* add a new entry */ + add_tl_element(rel, var); + } +} + +/***************************************************************************** + * + * JOININFO + * + *****************************************************************************/ + +/* + * initialize-join-clause-info-- + * Set the MergeSortable or HashJoinable field for every joininfo node + * (within a rel node) and the MergeSortOrder or HashJoinOp field for + * each clauseinfo node(within a joininfo node) for all relations in a + * query. + * + * Returns nothing. + */ +void +initialize_join_clause_info(List *rel_list) +{ + List *x, *y, *z; + Rel *rel; + JInfo *joininfo; + CInfo *clauseinfo; + Expr *clause; + + foreach (x, rel_list) { + rel = (Rel*)lfirst(x); + foreach (y, rel->joininfo) { + joininfo = (JInfo*)lfirst(y); + foreach (z, joininfo->jinfoclauseinfo) { + clauseinfo = (CInfo*)lfirst(z); + clause = clauseinfo->clause; + if(join_clause_p((Node*)clause)) { + MergeOrder *sortop = (MergeOrder*)NULL; + Oid hashop = (Oid)NULL; + + if (_enable_mergesort_) + sortop = mergesortop(clause); + if (_enable_hashjoin_) + hashop = hashjoinop(clause); + + if (sortop) { + clauseinfo->mergesortorder = sortop; + joininfo->mergesortable = true; + } + if (hashop) { + clauseinfo->hashjoinoperator = hashop; + joininfo->hashjoinable = true; + } + } + } + } + } +} + +/* + * mergesortop-- + * Returns the mergesort operator of an operator iff 'clause' is + * mergesortable, i.e., both operands are single vars and the operator is + * a mergesortable operator. + */ +static MergeOrder * +mergesortop(Expr *clause) +{ + Oid leftOp, rightOp; + bool sortable; + + sortable = op_mergesortable(((Oper*)clause->oper)->opno, + (get_leftop(clause))->vartype, + (get_rightop(clause))->vartype, + &leftOp, + &rightOp); + + if (sortable) { + MergeOrder *morder = makeNode(MergeOrder); + + morder->join_operator = ((Oper*)clause->oper)->opno; + morder->left_operator = leftOp; + morder->right_operator = rightOp; + morder->left_type = (get_leftop(clause))->vartype; + morder->right_type = (get_rightop(clause))->vartype; + return (morder); + } else + return(NULL); +} + +/* + * hashjoinop-- + * Returns the hashjoin operator of an operator iff 'clause' is + * hashjoinable, i.e., both operands are single vars and the operator is + * a hashjoinable operator. + */ +static Oid +hashjoinop(Expr *clause) +{ + return(op_hashjoinable(((Oper*)clause->oper)->opno, + (get_leftop(clause))->vartype, + (get_rightop(clause))->vartype)); +} diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c new file mode 100644 index 00000000000..5740b83a2d3 --- /dev/null +++ b/src/backend/optimizer/plan/planmain.c @@ -0,0 +1,422 @@ +/*------------------------------------------------------------------------- + * + * planmain.c-- + * Routines to plan a single query + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/parsenodes.h" +#include "nodes/relation.h" + +#include "optimizer/planmain.h" +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/clauses.h" +#include "optimizer/keys.h" +#include "optimizer/tlist.h" +#include "optimizer/xfunc.h" +#include "optimizer/cost.h" + +#include "tcop/dest.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "nodes/memnodes.h" +#include "utils/mcxt.h" +#include "utils/lsyscache.h" + +static Plan *subplanner(Query *root, List *flat_tlist, List *qual); +static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan); + +static Plan *make_groupPlan(List *tlist, bool tuplePerGroup, + List *groupClause, Plan *subplan); + +/* + * query_planner-- + * Routine to create a query plan. It does so by first creating a + * subplan for the topmost level of attributes in the query. Then, + * it modifies all target list and qualifications to consider the next + * level of nesting and creates a plan for this modified query by + * recursively calling itself. The two pieces are then merged together + * by creating a result node that indicates which attributes should + * be placed where and any relation level qualifications to be + * satisfied. + * + * command-type is the query command, e.g., retrieve, delete, etc. + * tlist is the target list of the query + * qual is the qualification of the query + * + * Returns a query plan. + */ +Plan * +query_planner(Query *root, + int command_type, + List *tlist, + List *qual) +{ + List *constant_qual = NIL; + List *flattened_tlist = NIL; + List *level_tlist = NIL; + Plan *subplan = (Plan*)NULL; + Agg *aggplan = NULL; + + /* + * A command without a target list or qualification is an error, + * except for "delete foo". + */ + if (tlist==NIL && qual==NULL) { + if (command_type == CMD_DELETE || + /* Total hack here. I don't know how to handle + statements like notify in action bodies. + Notify doesn't return anything but + scans a system table. */ + command_type == CMD_NOTIFY) { + return ((Plan*)make_seqscan(NIL, + NIL, + root->resultRelation, + (Plan*)NULL)); + } else + return((Plan*)NULL); + } + + /* + * Pull out any non-variable qualifications so these can be put in + * the topmost result node. The opids for the remaining + * qualifications will be changed to regprocs later. + */ + qual = pull_constant_clauses(qual, &constant_qual); + fix_opids(constant_qual); + + /* + * Create a target list that consists solely of (resdom var) target + * list entries, i.e., contains no arbitrary expressions. + */ + flattened_tlist = flatten_tlist(tlist); + if (flattened_tlist) { + level_tlist = flattened_tlist; + } else { + /* from old code. the logic is beyond me. - ay 2/95 */ + level_tlist = tlist; + } + + /* + * Needs to add the group attribute(s) to the target list so that they + * are available to either the Group node or the Agg node. (The target + * list may not contain the group attribute(s).) + */ + if (root->groupClause) { + AddGroupAttrToTlist(level_tlist, root->groupClause); + } + + if (root->qry_aggs) { + aggplan = make_agg(tlist, root->qry_numAgg, root->qry_aggs); + tlist = level_tlist; + } + + /* + * A query may have a non-variable target list and a non-variable + * qualification only under certain conditions: + * - the query creates all-new tuples, or + * - the query is a replace (a scan must still be done in this case). + */ + if (flattened_tlist==NULL && qual==NULL) { + + switch (command_type) { + case CMD_SELECT: + case CMD_INSERT: + return ((Plan*)make_result(tlist, + (Node*)constant_qual, + (Plan*)NULL)); + break; + + case CMD_DELETE: + case CMD_UPDATE: + { + SeqScan *scan = make_seqscan(tlist, + (List *)NULL, + root->resultRelation, + (Plan*)NULL); + if (constant_qual!=NULL) { + return ((Plan*)make_result(tlist, + (Node*)constant_qual, + (Plan*)scan)); + } else { + return ((Plan*)scan); + } + } + break; + + default: + return ((Plan*)NULL); + } + } + + /* + * Find the subplan (access path) and destructively modify the + * target list of the newly created subplan to contain the appropriate + * join references. + */ + subplan = subplanner(root, level_tlist, qual); + + set_tlist_references(subplan); + + /* + * If we have a GROUP BY clause, insert a group node (with the appropriate + * sort node.) + */ + if (root->groupClause != NULL) { + bool tuplePerGroup; + + /* + * decide whether how many tuples per group the Group node needs + * to return. (Needs only one tuple per group if no aggregate is + * present. Otherwise, need every tuple from the group to do the + * aggregation.) + */ + tuplePerGroup = (aggplan == NULL) ? FALSE : TRUE; + + subplan = + make_groupPlan(tlist, tuplePerGroup, root->groupClause, subplan); + + /* XXX fake it: this works for the Group node too! very very ugly, + please change me -ay 2/95 */ + set_agg_tlist_references((Agg*)subplan); + } + + /* + * If aggregate is present, insert the agg node + */ + if (aggplan != NULL) { + aggplan->plan.lefttree = subplan; + subplan = (Plan*)aggplan; + + /* + * set the varno/attno entries to the appropriate references to + * the result tuple of the subplans. (We need to set those in the + * array of aggreg's in the Agg node also. Even though they're + * pointers, after a few dozen's of copying, they're not the same as + * those in the target list.) + */ + set_agg_tlist_references((Agg*)subplan); + set_agg_agglist_references((Agg*)subplan); + + tlist = aggplan->plan.targetlist; + } + + /* + * Build a result node linking the plan if we have constant quals + */ + if (constant_qual) { + Plan *plan; + + plan = (Plan*)make_result(tlist, + (Node*)constant_qual, + subplan); + /* + * Change all varno's of the Result's node target list. + */ + set_result_tlist_references((Result*)plan); + + return (plan); + } + + /* + * fix up the flattened target list of the plan root node so that + * expressions are evaluated. this forces expression evaluations + * that may involve expensive function calls to be delayed to + * the very last stage of query execution. this could be bad. + * but it is joey's responsibility to optimally push these + * expressions down the plan tree. -- Wei + */ + subplan->targetlist = flatten_tlist_vars(tlist, + subplan->targetlist); + + /* + * Destructively modify the query plan's targetlist to add fjoin + * lists to flatten functions that return sets of base types + */ + subplan->targetlist = generate_fjoin(subplan->targetlist); + + return (subplan); +} + +/* + * subplanner + * + * Subplanner creates an entire plan consisting of joins and scans + * for processing a single level of attributes. + * + * flat-tlist is the flattened target list + * qual is the qualification to be satisfied + * + * Returns a subplan. + * + */ +static Plan * +subplanner(Query *root, + List *flat_tlist, + List *qual) +{ + Rel *final_relation; + List *final_relation_list; + + /* Initialize the targetlist and qualification, adding entries to + * *query-relation-list* as relation references are found (e.g., in the + * qualification, the targetlist, etc.) + */ + root->base_relation_list_ = NIL; + root->join_relation_list_ = NIL; + initialize_base_rels_list(root, flat_tlist); + initialize_base_rels_jinfo(root, qual); + add_missing_vars_to_base_rels(root, flat_tlist); + + /* Find all possible scan and join paths. + * Mark all the clauses and relations that can be processed using special + * join methods, then do the exhaustive path search. + */ + initialize_join_clause_info(root->base_relation_list_); + final_relation_list = find_paths(root, + root->base_relation_list_); + + if (final_relation_list) + final_relation = (Rel*)lfirst (final_relation_list); + else + final_relation = (Rel*)NIL; + +#if 0 /* fix xfunc */ + /* + * Perform Predicate Migration on each path, to optimize and correctly + * assess the cost of each before choosing the cheapest one. + * -- JMH, 11/16/92 + * + * Needn't do so if the top rel is pruneable: that means there's no + * expensive functions left to pull up. -- JMH, 11/22/92 + */ + if (XfuncMode != XFUNC_OFF && XfuncMode != XFUNC_NOPM && + XfuncMode != XFUNC_NOPULL && !final_relation->pruneable) + { + List *pathnode; + foreach(pathnode, final_relation->pathlist) + { + if (xfunc_do_predmig((Path*)lfirst(pathnode))) + set_cheapest(final_relation, final_relation->pathlist); + } + } +#endif + + /* + * Determine the cheapest path and create a subplan corresponding to it. + */ + if (final_relation) { + return (create_plan ((Path*)final_relation->cheapestpath)); + }else { + elog(NOTICE, "final relation is nil"); + return(create_plan ((Path*)NULL)); + } + +} + +/***************************************************************************** + * + *****************************************************************************/ + +static Result * +make_result(List *tlist, + Node *resconstantqual, + Plan *subplan) +{ + Result *node = makeNode(Result); + Plan *plan = &node->plan; + + tlist = generate_fjoin(tlist); + plan->cost = 0.0; + plan->state = (EState *)NULL; + plan->targetlist = tlist; + plan->lefttree = subplan; + plan->righttree = NULL; + node->resconstantqual = resconstantqual; + node->resstate = NULL; + + return(node); +} + +/***************************************************************************** + * + *****************************************************************************/ + +static Plan * +make_groupPlan(List *tlist, + bool tuplePerGroup, + List *groupClause, + Plan *subplan) +{ + List *sort_tlist; + List *gl; + int keyno; + Sort *sortplan; + Group *grpplan; + int numCols; + AttrNumber *grpColIdx; + + numCols = length(groupClause); + grpColIdx = (AttrNumber *)palloc(sizeof(AttrNumber)*numCols); + + /* + * first, make a sort node. Group node expects the tuples it gets + * from the subplan is in the order as specified by the group columns. + */ + keyno = 1; + sort_tlist = new_unsorted_tlist(subplan->targetlist); + + { + /* if this is a mergejoin node, varno could be OUTER/INNER */ + List *l; + foreach(l, sort_tlist) { + TargetEntry *tle; + tle = lfirst(l); + ((Var*)tle->expr)->varno = 1; + } + } + + foreach (gl, groupClause) { + GroupClause *grpcl = (GroupClause*)lfirst(gl); + TargetEntry *tle; + + tle = match_varid(grpcl->grpAttr, sort_tlist); + /* + * the parser should have checked to make sure the group attribute + * is valid but the optimizer might have screwed up and hence we + * check again. + */ + if (tle==NULL) { + elog(WARN, "group attribute disappeared from target list"); + } + tle->resdom->reskey = keyno; + tle->resdom->reskeyop = get_opcode(grpcl->grpOpoid); + + grpColIdx[keyno-1] = tle->resdom->resno; + keyno++; + } + sortplan = make_sort(sort_tlist, + _TEMP_RELATION_ID_, + subplan, + numCols); + sortplan->plan.cost = subplan->cost; /* XXX assume no cost */ + + /* + * make the Group node + */ + tlist = copyObject(tlist); /* make a copy */ + grpplan = make_group(tlist, tuplePerGroup, numCols, grpColIdx, sortplan); + + return (Plan*)grpplan; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c new file mode 100644 index 00000000000..4e57af8aa27 --- /dev/null +++ b/src/backend/optimizer/plan/planner.c @@ -0,0 +1,408 @@ +/*------------------------------------------------------------------------- + * + * planner.c-- + * The query optimizer external interface. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/parsenodes.h" +#include "nodes/relation.h" + +#include "parser/catalog_utils.h" +#include "parser/parse_query.h" +#include "utils/elog.h" +#include "utils/lsyscache.h" +#include "access/heapam.h" + +#include "optimizer/internal.h" +#include "optimizer/planner.h" +#include "optimizer/plancat.h" +#include "optimizer/prep.h" +#include "optimizer/planmain.h" +#include "optimizer/paths.h" +#include "optimizer/cost.h" + +/* DATA STRUCTURE CREATION/MANIPULATION ROUTINES */ +#include "nodes/relation.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/joininfo.h" +#include "optimizer/keys.h" +#include "optimizer/ordering.h" +#include "optimizer/pathnode.h" +#include "optimizer/clauses.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" + +#include "executor/executor.h" + +static Plan *make_sortplan(List *tlist, List *sortcls, Plan *plannode); +static Plan *init_query_planner(Query *parse); +static Existential *make_existential(Plan *left, Plan *right); + +/***************************************************************************** + * + * Query optimizer entry point + * + *****************************************************************************/ + + +/* + * planner-- + * Main query optimizer routine. + * + * Invokes the planner on union queries if there are any left, + * recursing if necessary to get them all, then processes normal plans. + * + * Returns a query plan. + * + */ +Plan* +planner(Query *parse) +{ + List *tlist = parse->targetList; + List *rangetable = parse->rtable; + char* uniqueflag = parse->uniqueFlag; + List *sortclause = parse->sortClause; + Plan *special_plans = (Plan*)NULL; + + Plan *result_plan = (Plan*) NULL; + + int rt_index; + + /* + * plan inheritance + */ + rt_index = first_matching_rt_entry(rangetable, INHERITS_FLAG); + if (rt_index != -1) { + special_plans = (Plan *)plan_union_queries((Index)rt_index, + parse, + INHERITS_FLAG); + } + + /* + * plan archive queries + */ + rt_index = first_matching_rt_entry(rangetable, ARCHIVE_FLAG); + if (rt_index != -1) { + special_plans = (Plan *)plan_union_queries((Index)rt_index, + parse, + ARCHIVE_FLAG); + } + + if (special_plans) + result_plan = special_plans; + else + result_plan = init_query_planner(parse); /* regular plans */ + + /* + * For now, before we hand back the plan, check to see if there + * is a user-specified sort that needs to be done. Eventually, this + * will be moved into the guts of the planner s.t. user specified + * sorts will be considered as part of the planning process. + * Since we can only make use of user-specified sorts in + * special cases, we can do the optimization step later. + */ + + if (uniqueflag) { + Plan *sortplan = make_sortplan(tlist, sortclause, result_plan); + + return((Plan*)make_unique(tlist,sortplan,uniqueflag)); + } else { + if (sortclause) + return(make_sortplan(tlist,sortclause,result_plan)); + else + return((Plan*)result_plan); + } + +} + +/* + * make_sortplan-- + * Returns a sortplan which is basically a SORT node attached to the + * top of the plan returned from the planner. It also adds the + * cost of sorting into the plan. + * + * sortkeys: ( resdom1 resdom2 resdom3 ...) + * sortops: (sortop1 sortop2 sortop3 ...) + */ +static Plan * +make_sortplan(List *tlist, List *sortcls, Plan *plannode) +{ + Plan *sortplan = (Plan*)NULL; + List *temp_tlist = NIL; + List *i = NIL; + Resdom *resnode = (Resdom*)NULL; + Resdom *resdom = (Resdom*)NULL; + int keyno =1; + + /* First make a copy of the tlist so that we don't corrupt the + * the original . + */ + + temp_tlist = new_unsorted_tlist(tlist); + + foreach (i, sortcls) { + SortClause *sortcl = (SortClause*)lfirst(i); + + resnode = sortcl->resdom; + resdom = tlist_resdom(temp_tlist, resnode); + + /* Order the resdom keys and replace the operator OID for each + * key with the regproc OID. + */ + resdom->reskey = keyno; + resdom->reskeyop = get_opcode(sortcl->opoid); + keyno += 1; + } + + sortplan = (Plan*)make_sort(temp_tlist, + _TEMP_RELATION_ID_, + (Plan*)plannode, + length(sortcls)); + + /* + * XXX Assuming that an internal sort has no. cost. + * This is wrong, but given that at this point, we don't + * know the no. of tuples returned, etc, we can't do + * better than to add a constant cost. + * This will be fixed once we move the sort further into the planner, + * but for now ... functionality.... + */ + + sortplan->cost = plannode->cost; + + return(sortplan); +} + + +/* + * init-query-planner-- + * Deals with all non-union preprocessing, including existential + * qualifications and CNFifying the qualifications. + * + * Returns a query plan. + * MODIFIES: tlist,qual + * + */ +static Plan * +init_query_planner(Query *root) +{ + List *primary_qual; + List *existential_qual; + Existential *exist_plan; + List *tlist = root->targetList; + + tlist = preprocess_targetlist(tlist, + root->commandType, + root->resultRelation, + root->rtable); + + primary_qual = + preprocess_qualification((Expr*)root->qual, + tlist, + &existential_qual); + + if(existential_qual==NULL) { + return(query_planner(root, + root->commandType, + tlist, + primary_qual)); + } else { + int temp = root->commandType; + Plan *existential_plan; + + root->commandType = CMD_SELECT; + existential_plan = query_planner(root, + temp, + NIL, + existential_qual); + + exist_plan = make_existential(existential_plan, + query_planner(root, + root->commandType, + tlist, + primary_qual)); + return((Plan*)exist_plan); + } +} + +/* + * make_existential-- + * Instantiates an existential plan node and fills in + * the left and right subtree slots. + */ +static Existential * +make_existential(Plan *left, Plan *right) +{ + Existential *node = makeNode(Existential); + + node->lefttree = left; + node->righttree = left; + return(node); +} + +/* + * pg_checkretval() -- check return value of a list of sql parse + * trees. + * + * The return value of a sql function is the value returned by + * the final query in the function. We do some ad-hoc define-time + * type checking here to be sure that the user is returning the + * type he claims. + */ +void +pg_checkretval(Oid rettype, QueryTreeList *queryTreeList) +{ + Query *parse; + List *tlist; + List *rt; + int cmd; + Type typ; + Resdom *resnode; + Relation reln; + Oid relid; + Oid tletype; + int relnatts; + int i; + + /* find the final query */ + parse = queryTreeList->qtrees[queryTreeList->len - 1]; + + /* + * test 1: if the last query is a utility invocation, then there + * had better not be a return value declared. + */ + if (parse->commandType == CMD_UTILITY) { + if (rettype == InvalidOid) + return; + else + elog(WARN, "return type mismatch in function decl: final query is a catalog utility"); + } + + /* okay, it's an ordinary query */ + tlist = parse->targetList; + rt = parse->rtable; + cmd = parse->commandType; + + /* + * test 2: if the function is declared to return no value, then the + * final query had better not be a retrieve. + */ + if (rettype == InvalidOid) { + if (cmd == CMD_SELECT) + elog(WARN, + "function declared with no return type, but final query is a retrieve"); + else + return; + } + + /* by here, the function is declared to return some type */ + if ((typ = (Type)get_id_type(rettype)) == NULL) + elog(WARN, "can't find return type %d for function\n", rettype); + + /* + * test 3: if the function is declared to return a value, then the + * final query had better be a retrieve. + */ + if (cmd != CMD_SELECT) + elog(WARN, "function declared to return type %s, but final query is not a retrieve", tname(typ)); + + /* + * test 4: for base type returns, the target list should have exactly + * one entry, and its type should agree with what the user declared. + */ + + if (get_typrelid(typ) == InvalidOid) { + if (exec_tlist_length(tlist) > 1) + elog(WARN, "function declared to return %s returns multiple values in final retrieve", tname(typ)); + + resnode = (Resdom*) ((TargetEntry*)lfirst(tlist))->resdom; + if (resnode->restype != rettype) + elog(WARN, "return type mismatch in function: declared to return %s, returns %s", tname(typ), tname(get_id_type(resnode->restype))); + + /* by here, base return types match */ + return; + } + + /* + * If the target list is of length 1, and the type of the varnode + * in the target list is the same as the declared return type, this + * is okay. This can happen, for example, where the body of the + * function is 'retrieve (x = func2())', where func2 has the same + * return type as the function that's calling it. + */ + if (exec_tlist_length(tlist) == 1) { + resnode = (Resdom*) ((TargetEntry*)lfirst(tlist))->resdom; + if (resnode->restype == rettype) + return; + } + + /* + * By here, the procedure returns a (set of) tuples. This part of + * the typechecking is a hack. We look up the relation that is + * the declared return type, and be sure that attributes 1 .. n + * in the target list match the declared types. + */ + reln = heap_open(get_typrelid(typ)); + + if (!RelationIsValid(reln)) + elog(WARN, "cannot open relation relid %d", get_typrelid(typ)); + + relid = reln->rd_id; + relnatts = reln->rd_rel->relnatts; + + if (exec_tlist_length(tlist) != relnatts) + elog(WARN, "function declared to return type %s does not retrieve (%s.*)", tname(typ), tname(typ)); + + /* expect attributes 1 .. n in order */ + for (i = 1; i <= relnatts; i++) { + TargetEntry *tle = lfirst(tlist); + Node *thenode = tle->expr; + + tlist = lnext(tlist); + tletype = exprType(thenode); + +#if 0 /* fix me */ + /* this is tedious */ + if (IsA(thenode,Var)) + tletype = (Oid) ((Var*)thenode)->vartype; + else if (IsA(thenode,Const)) + tletype = (Oid) ((Const*)thenode)->consttype; + else if (IsA(thenode,Param)) { + tletype = (Oid) ((Param*)thenode)->paramtype; + else if (IsA(thenode,Expr)) { + tletype = Expr + } + } else if (IsA(thenode,LispList)) { + thenode = lfirst(thenode); + if (IsA(thenode,Oper)) + tletype = (Oid) get_opresulttype((Oper*)thenode); + else if (IsA(thenode,Func)) + tletype = (Oid) get_functype((Func*)thenode); + else + elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ)); +#endif +/* + } else + elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ)); +*/ + /* reach right in there, why don't you? */ + if (tletype != reln->rd_att->attrs[i-1]->atttypid) + elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ)); + } + + heap_close(reln); + + /* success */ + return; +} diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c new file mode 100644 index 00000000000..e698c930e1d --- /dev/null +++ b/src/backend/optimizer/plan/setrefs.c @@ -0,0 +1,706 @@ +/*------------------------------------------------------------------------- + * + * setrefs.c-- + * Routines to change varno/attno entries to contain references + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" + +#include "utils/elog.h" +#include "nodes/nodeFuncs.h" +#include "nodes/makefuncs.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/keys.h" +#include "optimizer/planmain.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "optimizer/tlist.h" + +static void set_join_tlist_references(Join *join); +static void set_tempscan_tlist_references(SeqScan *tempscan); +static void set_temp_tlist_references(Temp *temp); +static List *replace_clause_joinvar_refs(Expr *clause, + List *outer_tlist, List *inner_tlist); +static List *replace_subclause_joinvar_refs(List *clauses, + List *outer_tlist, List *inner_tlist); +static Var *replace_joinvar_refs(Var *var, List *outer_tlist, List *inner_tlist); +static List *tlist_temp_references(Oid tempid, List *tlist); +static void replace_result_clause(List *clause, List *subplanTargetList); +static bool OperandIsInner(Node *opnd, int inner_relid); +static void replace_agg_clause(Node *expr, List *targetlist); + +/***************************************************************************** + * + * SUBPLAN REFERENCES + * + *****************************************************************************/ + +/* + * set-tlist-references-- + * Modifies the target list of nodes in a plan to reference target lists + * at lower levels. + * + * 'plan' is the plan whose target list and children's target lists will + * be modified + * + * Returns nothing of interest, but modifies internal fields of nodes. + * + */ +void +set_tlist_references(Plan *plan) +{ + if(plan==NULL) + return; + + if (IsA_Join(plan)) { + set_join_tlist_references((Join*)plan); + } else if (IsA(plan,SeqScan) && plan->lefttree && + IsA_Temp(plan->lefttree)) { + set_tempscan_tlist_references((SeqScan*)plan); + } else if (IsA(plan,Sort)) { + set_temp_tlist_references ((Temp*)plan); + } else if (IsA(plan,Result)) { + set_result_tlist_references((Result*)plan); + } else if (IsA(plan,Hash)) { + set_tlist_references(plan->lefttree); + } else if (IsA(plan,Choose)) { + List *x; + foreach (x, ((Choose*)plan)->chooseplanlist) { + set_tlist_references((Plan*)lfirst(x)); + } + } +} + +/* + * set-join-tlist-references-- + * Modifies the target list of a join node by setting the varnos and + * varattnos to reference the target list of the outer and inner join + * relations. + * + * Creates a target list for a join node to contain references by setting + * varno values to OUTER or INNER and setting attno values to the + * result domain number of either the corresponding outer or inner join + * tuple. + * + * 'join' is a join plan node + * + * Returns nothing of interest, but modifies internal fields of nodes. + * + */ +static void +set_join_tlist_references(Join *join) +{ + Plan *outer = ((Plan*)join)->lefttree; + Plan *inner = ((Plan*)join)->righttree; + List *new_join_targetlist = NIL; + TargetEntry *temp = (TargetEntry *)NULL; + List *entry = NIL; + List *inner_tlist = NULL; + List *outer_tlist = NULL; + TargetEntry *xtl = (TargetEntry *)NULL; + List *qptlist = ((Plan*)join)->targetlist; + + foreach(entry, qptlist) { + List *joinvar; + + xtl = (TargetEntry *)lfirst(entry); + inner_tlist = ((inner==NULL) ? NIL : inner->targetlist); + outer_tlist = ((outer==NULL) ? NIL : outer->targetlist); + joinvar = replace_clause_joinvar_refs((Expr*)get_expr(xtl), + outer_tlist, + inner_tlist); + + temp = MakeTLE(xtl->resdom, (Node*)joinvar); + new_join_targetlist = lappend(new_join_targetlist,temp); + } + + ((Plan*)join)->targetlist = new_join_targetlist; + if (outer!=NULL) + set_tlist_references(outer); + if (inner!=NULL) + set_tlist_references(inner); +} + +/* + * set-tempscan-tlist-references-- + * Modifies the target list of a node that scans a temp relation (i.e., a + * sort or hash node) so that the varnos refer to the child temporary. + * + * 'tempscan' is a seqscan node + * + * Returns nothing of interest, but modifies internal fields of nodes. + * + */ +static void +set_tempscan_tlist_references(SeqScan *tempscan) +{ + Temp *temp = (Temp*)((Plan*)tempscan)->lefttree; + + ((Plan*)tempscan)->targetlist = + tlist_temp_references(temp->tempid, + ((Plan*)tempscan)->targetlist); + set_temp_tlist_references(temp); +} + +/* + * set-temp-tlist-references-- + * The temp's vars are made consistent with (actually, identical to) the + * modified version of the target list of the node from which temp node + * receives its tuples. + * + * 'temp' is a temp (e.g., sort, hash) plan node + * + * Returns nothing of interest, but modifies internal fields of nodes. + * + */ +static void +set_temp_tlist_references(Temp *temp) +{ + Plan *source = ((Plan*)temp)->lefttree; + + if (source!=NULL) { + set_tlist_references(source); + ((Plan*)temp)->targetlist = + copy_vars(((Plan*)temp)->targetlist , + (source)->targetlist); + } else { + elog(WARN, "calling set_temp_tlist_references with empty lefttree"); + } +} + +/* + * join-references-- + * Creates a new set of join clauses by replacing the varno/varattno + * values of variables in the clauses to reference target list values + * from the outer and inner join relation target lists. + * + * 'clauses' is the list of join clauses + * 'outer-tlist' is the target list of the outer join relation + * 'inner-tlist' is the target list of the inner join relation + * + * Returns the new join clauses. + * + */ +List * +join_references(List *clauses, + List *outer_tlist, + List *inner_tlist) +{ + return (replace_subclause_joinvar_refs(clauses, + outer_tlist, + inner_tlist)); +} + +/* + * index-outerjoin-references-- + * Given a list of join clauses, replace the operand corresponding to the + * outer relation in the join with references to the corresponding target + * list element in 'outer-tlist' (the outer is rather obscurely + * identified as the side that doesn't contain a var whose varno equals + * 'inner-relid'). + * + * As a side effect, the operator is replaced by the regproc id. + * + * 'inner-indxqual' is the list of join clauses (so-called because they + * are used as qualifications for the inner (inbex) scan of a nestloop) + * + * Returns the new list of clauses. + * + */ +List * +index_outerjoin_references(List *inner_indxqual, + List *outer_tlist, + Index inner_relid) +{ + List *t_list = NIL; + Expr *temp = NULL; + List *t_clause = NIL; + Expr *clause = NULL; + + foreach (t_clause,inner_indxqual) { + clause = lfirst(t_clause); + /* + * if inner scan on the right. + */ + if (OperandIsInner((Node*)get_rightop(clause), inner_relid)) { + Var *joinvar = (Var*) + replace_clause_joinvar_refs((Expr*)get_leftop(clause), + outer_tlist, + NIL); + temp = make_opclause(replace_opid((Oper*)((Expr*)clause)->oper), + joinvar, + get_rightop(clause)); + t_list = lappend(t_list,temp); + } else { + /* inner scan on left */ + Var *joinvar = (Var*) + replace_clause_joinvar_refs((Expr*)get_rightop(clause), + outer_tlist, + NIL); + temp = make_opclause(replace_opid((Oper*)((Expr*)clause)->oper), + joinvar, + get_leftop(clause)); + t_list = lappend(t_list,temp); + } + + } + return(t_list); +} + +/* + * replace-clause-joinvar-refs + * replace-subclause-joinvar-refs + * replace-joinvar-refs + * + * Replaces all variables within a join clause with a new var node + * whose varno/varattno fields contain a reference to a target list + * element from either the outer or inner join relation. + * + * 'clause' is the join clause + * 'outer-tlist' is the target list of the outer join relation + * 'inner-tlist' is the target list of the inner join relation + * + * Returns the new join clause. + * + */ +static List * +replace_clause_joinvar_refs(Expr *clause, + List *outer_tlist, + List *inner_tlist) +{ + List *temp = NULL; + + if(IsA (clause,Var)) { + temp = (List*)replace_joinvar_refs((Var*)clause, + outer_tlist,inner_tlist); + if(temp) + return(temp); + else + if (clause != NULL) + return((List*)clause); + else + return(NIL); + } else if (single_node((Node*)clause)) { + return ((List*)clause); + } else if (or_clause((Node*)clause)) { + List *orclause = + replace_subclause_joinvar_refs(((Expr*)clause)->args, + outer_tlist, + inner_tlist); + return ((List*)make_orclause(orclause)); + } else if (IsA(clause,ArrayRef)) { + ArrayRef *aref = (ArrayRef *)clause; + + temp = replace_subclause_joinvar_refs(aref->refupperindexpr, + outer_tlist, + inner_tlist); + aref->refupperindexpr = (List*)temp; + temp = replace_subclause_joinvar_refs(aref->reflowerindexpr, + outer_tlist, + inner_tlist); + aref->reflowerindexpr = (List*)temp; + temp = replace_clause_joinvar_refs((Expr*)aref->refexpr, + outer_tlist, + inner_tlist); + aref->refexpr = (Node*)temp; + + /* + * no need to set refassgnexpr. we only set that in the + * target list on replaces, and this is an array reference + * in the qualification. if we got this far, it's 0x0 in + * the ArrayRef structure 'clause'. + */ + + return((List*)clause); + } else if (is_funcclause((Node*)clause)) { + List *funcclause = + replace_subclause_joinvar_refs(((Expr*)clause)->args, + outer_tlist, + inner_tlist); + return ((List*)make_funcclause((Func*)((Expr*)clause)->oper, + funcclause)); + } else if (not_clause((Node*)clause)) { + List *notclause = + replace_clause_joinvar_refs(get_notclausearg(clause), + outer_tlist, + inner_tlist); + return ((List*)make_notclause((Expr*)notclause)); + } else if (is_opclause((Node*)clause)) { + Var *leftvar = + (Var*)replace_clause_joinvar_refs((Expr*)get_leftop(clause), + outer_tlist, + inner_tlist); + Var *rightvar = + (Var*)replace_clause_joinvar_refs((Expr*)get_rightop(clause), + outer_tlist, + inner_tlist); + return ((List*)make_opclause(replace_opid((Oper*)((Expr*)clause)->oper), + leftvar, + rightvar)); + } + /* shouldn't reach here */ + return NULL; +} + +static List * +replace_subclause_joinvar_refs(List *clauses, + List *outer_tlist, + List *inner_tlist) +{ + List *t_list = NIL; + List *temp = NIL; + List *clause = NIL; + + foreach (clause,clauses) { + temp = replace_clause_joinvar_refs(lfirst(clause), + outer_tlist, + inner_tlist); + t_list = lappend(t_list,temp); + } + return(t_list); +} + +static Var * +replace_joinvar_refs(Var *var, List *outer_tlist, List *inner_tlist) +{ + Resdom *outer_resdom =(Resdom*)NULL; + + outer_resdom= tlist_member(var,outer_tlist); + + if (outer_resdom!=NULL && IsA (outer_resdom,Resdom) ) { + return (makeVar (OUTER, + outer_resdom->resno, + var->vartype, + var->varnoold, + var->varoattno)); + } else { + Resdom *inner_resdom; + inner_resdom = tlist_member(var,inner_tlist); + if ( inner_resdom!=NULL && IsA (inner_resdom,Resdom) ) { + return (makeVar (INNER, + inner_resdom->resno, + var->vartype, + var->varnoold, + var->varoattno)); + } + } + return (Var*)NULL; +} + +/* + * tlist-temp-references-- + * Creates a new target list for a node that scans a temp relation, + * setting the varnos to the id of the temp relation and setting varids + * if necessary (varids are only needed if this is a targetlist internal + * to the tree, in which case the targetlist entry always contains a var + * node, so we can just copy it from the temp). + * + * 'tempid' is the id of the temp relation + * 'tlist' is the target list to be modified + * + * Returns new target list + * + */ +static List * +tlist_temp_references(Oid tempid, + List *tlist) +{ + List *t_list = NIL; + TargetEntry *temp = (TargetEntry *)NULL; + TargetEntry *xtl = NULL; + List *entry; + + foreach (entry, tlist) { + AttrNumber oattno; + + xtl = lfirst(entry); + if (IsA(get_expr(xtl), Var)) + oattno = ((Var*)xtl->expr)->varoattno; + else + oattno = 0; + + temp = MakeTLE(xtl->resdom, + (Node*)makeVar(tempid, + xtl->resdom->resno, + xtl->resdom->restype, + tempid, + oattno)); + + t_list = lappend(t_list,temp); + } + return(t_list); +} + +/*--------------------------------------------------------- + * + * set_result_tlist_references + * + * Change the target list of a Result node, so that it correctly + * addresses the tuples returned by its left tree subplan. + * + * NOTE: + * 1) we ignore the right tree! (in the current implementation + * it is always nil + * 2) this routine will probably *NOT* work with nested dot + * fields.... + */ +void +set_result_tlist_references(Result *resultNode) +{ + Plan *subplan; + List *resultTargetList; + List *subplanTargetList; + List *t; + TargetEntry *entry; + Expr *expr; + + resultTargetList= ((Plan*)resultNode)->targetlist; + + /* + * NOTE: we only consider the left tree subplan. + * This is usually a seq scan. + */ + subplan = ((Plan*)resultNode)->lefttree; + if (subplan != NULL) { + subplanTargetList = subplan->targetlist; + } else { + subplanTargetList = NIL; + } + + /* + * now for traverse all the entris of the target list. + * These should be of the form (Resdom_Node Expression). + * For every expression clause, call "replace_result_clause()" + * to appropriatelly change all the Var nodes. + */ + foreach (t, resultTargetList) { + entry = (TargetEntry *)lfirst(t); + expr = (Expr*) get_expr(entry); + replace_result_clause((List*)expr, subplanTargetList); + } +} + +/*--------------------------------------------------------- + * + * replace_result_clause + * + * This routine is called from set_result_tlist_references(). + * and modifies the expressions of the target list of a Result + * node so that all Var nodes reference the target list of its subplan. + * + */ +static void +replace_result_clause(List *clause, + List *subplanTargetList) /* target list of the + subplan */ +{ + List *t; + List *subClause; + TargetEntry *subplanVar; + + if (IsA(clause,Var)) { + /* + * Ha! A Var node! + */ + subplanVar = match_varid((Var*)clause, subplanTargetList); + /* + * Change the varno & varattno fields of the + * var node. + * + */ + ((Var*)clause)->varno = (Index)OUTER; + ((Var*)clause)->varattno = subplanVar->resdom->resno; + } else if (is_funcclause((Node*)clause)) { + /* + * This is a function. Recursively call this routine + * for its arguments... + */ + subClause = ((Expr*)clause)->args; + foreach (t, subClause) { + replace_result_clause(lfirst(t),subplanTargetList); + } + } else if (IsA(clause,ArrayRef)) { + ArrayRef *aref = (ArrayRef *)clause; + /* + * This is an arrayref. Recursively call this routine + * for its expression and its index expression... + */ + subClause = aref->refupperindexpr; + foreach (t, subClause) { + replace_result_clause(lfirst(t),subplanTargetList); + } + subClause = aref->reflowerindexpr; + foreach (t, subClause) { + replace_result_clause(lfirst(t),subplanTargetList); + } + replace_result_clause((List*)aref->refexpr, + subplanTargetList); + replace_result_clause((List*)aref->refassgnexpr, + subplanTargetList); + } else if (is_opclause((Node*)clause)) { + /* + * This is an operator. Recursively call this routine + * for both its left and right operands + */ + subClause = (List*)get_leftop((Expr*)clause); + replace_result_clause(subClause,subplanTargetList); + subClause = (List*)get_rightop((Expr*)clause); + replace_result_clause(subClause,subplanTargetList); + } else if (IsA(clause,Param) || IsA(clause,Const)) { + /* do nothing! */ + } else { + /* + * Ooops! we can not handle that! + */ + elog(WARN,"replace_result_clause: Can not handle this tlist!\n"); + } +} + +static +bool OperandIsInner(Node *opnd, int inner_relid) +{ + /* + * Can be the inner scan if its a varnode or a function and the + * inner_relid is equal to the varnode's var number or in the + * case of a function the first argument's var number (all args + * in a functional index are from the same relation). + */ + if ( IsA (opnd,Var) && + (inner_relid == ((Var*)opnd)->varno) ) + { + return true; + } + if (is_funcclause(opnd)) + { + List *firstArg = lfirst(((Expr*)opnd)->args); + + if ( IsA (firstArg,Var) && + (inner_relid == ((Var*)firstArg)->varno) ) + { + return true; + } + } + return false; +} + +/***************************************************************************** + * + *****************************************************************************/ + +/*--------------------------------------------------------- + * + * set_agg_tlist_references - + * changes the target list of an Agg node so that it points to + * the tuples returned by its left tree subplan. + * + */ +void +set_agg_tlist_references(Agg *aggNode) +{ + List *aggTargetList; + List *subplanTargetList; + List *tl; + + aggTargetList = aggNode->plan.targetlist; + subplanTargetList = aggNode->plan.lefttree->targetlist; + + foreach (tl, aggTargetList) { + TargetEntry *tle = lfirst(tl); + + replace_agg_clause(tle->expr, subplanTargetList); + } +} + +void +set_agg_agglist_references(Agg *aggNode) +{ + List *subplanTargetList; + Aggreg **aggs; + int i; + + aggs = aggNode->aggs; + subplanTargetList = aggNode->plan.lefttree->targetlist; + + for (i = 0; i < aggNode->numAgg; i++) { + replace_agg_clause(aggs[i]->target, subplanTargetList); + } +} + +static void +replace_agg_clause(Node *clause, List *subplanTargetList) +{ + List *t; + TargetEntry *subplanVar; + + if (IsA(clause,Var)) { + /* + * Ha! A Var node! + */ + subplanVar = match_varid((Var*)clause, subplanTargetList); + /* + * Change the varno & varattno fields of the + * var node. + * + */ + ((Var*)clause)->varattno = subplanVar->resdom->resno; + } else if (is_funcclause(clause)) { + /* + * This is a function. Recursively call this routine + * for its arguments... + */ + foreach (t, ((Expr*)clause)->args) { + replace_agg_clause(lfirst(t), subplanTargetList); + } + } else if (IsA(clause,Aggreg)) { + replace_agg_clause(((Aggreg*)clause)->target, subplanTargetList); + } else if (IsA(clause,ArrayRef)) { + ArrayRef *aref = (ArrayRef *)clause; + + /* + * This is an arrayref. Recursively call this routine + * for its expression and its index expression... + */ + foreach (t, aref->refupperindexpr) { + replace_agg_clause(lfirst(t),subplanTargetList); + } + foreach (t, aref->reflowerindexpr) { + replace_agg_clause(lfirst(t),subplanTargetList); + } + replace_agg_clause(aref->refexpr, subplanTargetList); + replace_agg_clause(aref->refassgnexpr, subplanTargetList); + } else if (is_opclause(clause)) { + /* + * This is an operator. Recursively call this routine + * for both its left and right operands + */ + replace_agg_clause((Node*)get_leftop((Expr*)clause), + subplanTargetList); + replace_agg_clause((Node*)get_rightop((Expr*)clause), + subplanTargetList); + } else if (IsA(clause,Param) || IsA(clause,Const)) { + /* do nothing! */ + } else { + /* + * Ooops! we can not handle that! + */ + elog(WARN,"replace_agg_clause: Can not handle this tlist!\n"); + } + +} + + diff --git a/src/backend/optimizer/plancat.h b/src/backend/optimizer/plancat.h new file mode 100644 index 00000000000..426778577de --- /dev/null +++ b/src/backend/optimizer/plancat.h @@ -0,0 +1,65 @@ +/*------------------------------------------------------------------------- + * + * plancat.h-- + * prototypes for plancat.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: plancat.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PLANCAT_H +#define PLANCAT_H + +#include "c.h" + +/* + * transient data structure to hold return value of index_info. Note that + * indexkeys, orderOprs and classlist is "null-terminated". + */ +typedef struct IdxInfoRetval { + Oid relid; /* OID of the index relation (not the OID + * of the relation being indexed) + */ + Oid relam; /* OID of the pg_am of this index */ + int pages; /* number of pages in the index relation */ + int tuples; /* number of tuples in the index relation */ + int *indexkeys; /* keys over which we're indexing */ + Oid *orderOprs; /* operators used for ordering purposes */ + Oid *classlist; /* classes of AM operators */ + Oid indproc; + Node *indpred; +} IdxInfoRetval; + + +extern void relation_info(Query *root, + Oid relid, + bool *hashindex, int *pages, + int *tuples); + +extern bool index_info(Query *root, + bool first, int relid, IdxInfoRetval *info); + +extern Cost +restriction_selectivity(Oid functionObjectId, + Oid operatorObjectId, + Oid relationObjectId, + AttrNumber attributeNumber, + char *constValue, + int32 constFlag); + +extern void +index_selectivity(Oid indid, Oid *classes, List *opnos, + Oid relid, List *attnos, List *values, List *flags, + int32 nkeys, float *idxPages, float *idxSelec); + +extern Cost join_selectivity(Oid functionObjectId, Oid operatorObjectId, + Oid relationObjectId1, AttrNumber attributeNumber1, + Oid relationObjectId2, AttrNumber attributeNumber2); + +extern List *find_inheritance_children(Oid inhparent); +extern List *VersionGetParents(Oid verrelid); + +#endif /* PLANCAT_H */ diff --git a/src/backend/optimizer/planmain.h b/src/backend/optimizer/planmain.h new file mode 100644 index 00000000000..b224e89550e --- /dev/null +++ b/src/backend/optimizer/planmain.h @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------- + * + * planmain.h-- + * prototypes for various files in optimizer/plan + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: planmain.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PLANMAIN_H +#define PLANMAIN_H + + +/* + * prototypes for plan/planmain.c + */ +extern Plan *query_planner(Query *root, + int command_type, List *tlist, List *qual); + + +/* + * prototypes for plan/createplan.c + */ +extern Plan *create_plan(Path *best_path); +extern SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid, + Plan *lefttree); +extern Sort *make_sort(List *tlist, Oid tempid, Plan *lefttree, + int keycount); +extern Agg *make_agg(List *tlist, int nagg, Aggreg **aggs); +extern Group *make_group(List *tlist, bool tuplePerGroup, int ngrp, + AttrNumber *grpColIdx, Sort *lefttree); +extern Unique *make_unique(List *tlist, Plan *lefttree, char *uniqueAttr); +extern List *generate_fjoin(List *tlist); + + +/* + * prototypes for plan/initsplan.c + */ +extern void initialize_base_rels_list(Query *root, List *tlist); +extern void initialize_base_rels_jinfo(Query *root, List *clauses); +extern void initialize_join_clause_info(List *rel_list); +extern void add_missing_vars_to_base_rels(Query *root, List *tlist); + +/* + * prototypes for plan/setrefs.c + */ +extern void set_tlist_references(Plan *plan); +extern List *join_references(List *clauses, List *outer_tlist, + List *inner_tlist); +extern List *index_outerjoin_references(List *inner_indxqual, + List *outer_tlist, Index inner_relid); +extern void set_result_tlist_references(Result *resultNode); +extern void set_agg_tlist_references(Agg *aggNode); +extern void set_agg_agglist_references(Agg *aggNode); + + +#endif /* PLANMAIN_H */ diff --git a/src/backend/optimizer/planner.h b/src/backend/optimizer/planner.h new file mode 100644 index 00000000000..5f049494802 --- /dev/null +++ b/src/backend/optimizer/planner.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * planner.h-- + * prototypes for planner.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: planner.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PLANNER_H +#define PLANNER_H + +/* +#include "optimizer/internal.h" +#include "parser/parse_query.h" +*/ + +extern Plan *planner(Query *parse); +extern void pg_checkretval(Oid rettype, QueryTreeList *querytree_list); + +#endif /* PLANNER_H */ diff --git a/src/backend/optimizer/prep.h b/src/backend/optimizer/prep.h new file mode 100644 index 00000000000..679097641fe --- /dev/null +++ b/src/backend/optimizer/prep.h @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * prep.h-- + * prototypes for files in prep.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: prep.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PREP_H +#define PREP_H + +#include "nodes/primnodes.h" +#include "nodes/plannodes.h" + +/* + * prototypes for archive.h + */ +extern void plan_archive(List *rt); +extern List *find_archive_rels(Oid relid); + +/* + * prototypes for prepqual.h + */ +extern List *preprocess_qualification(Expr *qual, List *tlist, + List **existentialQualPtr); +extern List *cnfify(Expr *qual, bool removeAndFlag); + +/* + * prototypes for preptlist.h + */ +extern List *preprocess_targetlist(List *tlist, int command_type, + Index result_relation, List *range_table); + +/* + * prototypes for prepunion.h + */ +typedef enum UnionFlag { + INHERITS_FLAG, ARCHIVE_FLAG, VERSION_FLAG +} UnionFlag; + +extern List *find_all_inheritors(List *unexamined_relids, + List *examined_relids); +extern int first_matching_rt_entry(List *rangetable, UnionFlag flag); +extern Append *plan_union_queries(Index rt_index, Query *parse, + UnionFlag flag); + +#endif /* PREP_H */ diff --git a/src/backend/optimizer/prep/Makefile.inc b/src/backend/optimizer/prep/Makefile.inc new file mode 100644 index 00000000000..40026716c9e --- /dev/null +++ b/src/backend/optimizer/prep/Makefile.inc @@ -0,0 +1,14 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for optimizer/prep +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/optimizer/prep/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:37 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= archive.c prepqual.c preptlist.c prepunion.c diff --git a/src/backend/optimizer/prep/archive.c b/src/backend/optimizer/prep/archive.c new file mode 100644 index 00000000000..0303eca70f1 --- /dev/null +++ b/src/backend/optimizer/prep/archive.c @@ -0,0 +1,66 @@ +/*------------------------------------------------------------------------- + * + * archive.c-- + * Support for planning scans on archived relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/Attic/archive.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> /* for sprintf() */ +#include <sys/types.h> /* for u_int in relcache.h */ +#include "postgres.h" + +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/relcache.h" +#include "catalog/pg_class.h" +#include "nodes/pg_list.h" +#include "nodes/parsenodes.h" +#include "optimizer/prep.h" +#include "commands/creatinh.h" + +void +plan_archive(List *rt) +{ + List *rtitem; + RangeTblEntry *rte; + TimeRange *trange; + Relation r; + Oid reloid; + + foreach(rtitem, rt) { + rte = lfirst(rtitem); + trange = rte->timeRange; + if (trange) { + reloid = rte->relid; + r = RelationIdGetRelation(reloid); + if (r->rd_rel->relarch != 'n') { + rte->archive = true; + } + } + } +} + + +/* + * find_archive_rels -- Given a particular relid, find the archive + * relation's relid. + */ +List * +find_archive_rels(Oid relid) +{ + Relation arel; + char *arelName; + + arelName = MakeArchiveName(relid); + arel = RelationNameGetRelation(arelName); + pfree(arelName); + + return lconsi(arel->rd_id, lconsi(relid, NIL)); +} diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c new file mode 100644 index 00000000000..e1aafa7db1e --- /dev/null +++ b/src/backend/optimizer/prep/prepqual.c @@ -0,0 +1,582 @@ +/*------------------------------------------------------------------------- + * + * prepqual.c-- + * Routines for preprocessing the parse tree qualification + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepqual.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/makefuncs.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/prep.h" + +#include "utils/lsyscache.h" + +static Expr *pull_args(Expr *qual); +static List *pull_ors(List *orlist); +static List *pull_ands(List *andlist); +static Expr *find_nots(Expr *qual); +static Expr *push_nots(Expr *qual); +static Expr *normalize(Expr *qual); +static List *or_normalize(List *orlist); +static List *distribute_args(List *item, List *args); +static List *qualcleanup(Expr *qual); +static List *remove_ands(Expr *qual); +static List *remove_duplicates(List *list); + +/* + * preprocess-qualification-- + * Driver routine for modifying the parse tree qualification. + * + * Returns the new base qualification and the existential qualification + * in existentialQualPtr. + * + * XXX right now, update_clauses() does nothing so + * preprocess-qualification simply converts the qual in conjunctive + * normal form (see cnfify() below ) + */ +List * +preprocess_qualification(Expr *qual, List *tlist, List **existentialQualPtr) +{ + List *cnf_qual = cnfify(qual, true); +/* + List *existential_qual = + update_clauses(intCons(_query_result_relation_, + update_relations(tlist)), + cnf_qual, + _query_command_type_); + if (existential_qual) { + *existentialQualPtr = existential_qual; + return set_difference(cnf_qual, existential_qual); + } else { + *existentialQualPtr = NIL; + return cnf_qual; + } +*/ + /* update_clauses() is not working right now */ + *existentialQualPtr = NIL; + return cnf_qual; + +} + +/***************************************************************************** + * + * CNF CONVERSION ROUTINES + * + * NOTES: + * The basic algorithms for normalizing the qualification are taken + * from ingres/source/qrymod/norml.c + * + * Remember that the initial qualification may consist of ARBITRARY + * combinations of clauses. In addition, before this routine is called, + * the qualification will contain explicit "AND"s. + * + *****************************************************************************/ + + +/* + * cnfify-- + * Convert a qualification to conjunctive normal form by applying + * successive normalizations. + * + * Returns the modified qualification with an extra level of nesting. + * + * If 'removeAndFlag' is true then it removes the explicit ANDs. + * + * NOTE: this routine is called by the planner (removeAndFlag = true) + * and from the rule manager (removeAndFlag = false). + * + */ +List * +cnfify(Expr *qual, bool removeAndFlag) +{ + Expr *newqual = NULL; + + if (qual != NULL) { + newqual = find_nots(pull_args(qual)); + newqual = normalize(pull_args(newqual)); + newqual = (Expr*)qualcleanup(pull_args(newqual)); + newqual = pull_args(newqual);; + + if (removeAndFlag) { + if(and_clause((Node*)newqual)) + newqual=(Expr*)remove_ands(newqual); + else + newqual=(Expr*)remove_ands(make_andclause(lcons(newqual,NIL))); + } + } + else if (qual!=NULL) + newqual = (Expr*)lcons(qual, NIL); + + return (List*)(newqual); +} + +/* + * pull-args-- + * Given a qualification, eliminate nested 'and' and 'or' clauses. + * + * Returns the modified qualification. + * + */ +static Expr * +pull_args(Expr *qual) +{ + if (qual==NULL) + return (NULL); + + if (is_opclause((Node*)qual)) { + return(make_clause(qual->opType, qual->oper, + lcons(pull_args((Expr*)get_leftop(qual)), + lcons(pull_args((Expr*)get_rightop(qual)), + NIL)))); + } else if (and_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach (temp, qual->args) + t_list = lappend (t_list, pull_args(lfirst(temp))); + return (make_andclause (pull_ands (t_list))); + }else if (or_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach (temp, qual->args) + t_list = lappend (t_list, pull_args(lfirst(temp))); + return (make_orclause (pull_ors (t_list))); + } else if (not_clause((Node*)qual)) { + return (make_notclause (pull_args (get_notclausearg (qual)))); + } else { + return (qual); + } +} + +/* + * pull-ors-- + * Pull the arguments of an 'or' clause nested within another 'or' + * clause up into the argument list of the parent. + * + * Returns the modified list. + */ +static List * +pull_ors(List *orlist) +{ + if (orlist==NIL) + return (NIL); + + if (or_clause(lfirst(orlist))) { + List *args = ((Expr*)lfirst(orlist))->args; + return (pull_ors(nconc(copyObject((Node*)args), + copyObject((Node*)lnext(orlist))))); + } else { + return (lcons(lfirst(orlist), pull_ors(lnext(orlist)))); + } +} + +/* + * pull-ands-- + * Pull the arguments of an 'and' clause nested within another 'and' + * clause up into the argument list of the parent. + * + * Returns the modified list. + */ +static List * +pull_ands(List *andlist) +{ + if (andlist==NIL) + return (NIL); + + if (and_clause (lfirst(andlist))) { + List *args = ((Expr*)lfirst(andlist))->args; + return (pull_ands(nconc(copyObject((Node*)args), + copyObject((Node*)lnext(andlist))))); + } else { + return (lcons(lfirst(andlist), pull_ands(lnext(andlist)))); + } +} + +/* + * find-nots-- + * Traverse the qualification, looking for 'not's to take care of. + * For 'not' clauses, remove the 'not' and push it down to the clauses' + * descendants. + * For all other clause types, simply recurse. + * + * Returns the modified qualification. + * + */ +static Expr * +find_nots(Expr *qual) +{ + if (qual==NULL) + return (NULL); + + if (is_opclause((Node*)qual)) { + return (make_clause(qual->opType, qual->oper, + lcons(find_nots((Expr*)get_leftop(qual)), + lcons(find_nots((Expr*)get_rightop(qual)), + NIL)))); + } else if (and_clause ((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach (temp, qual->args) { + t_list = lappend(t_list,find_nots(lfirst(temp))); + } + + return (make_andclause(t_list)); + } else if (or_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach (temp, qual->args) { + t_list = lappend(t_list,find_nots(lfirst(temp))); + } + return (make_orclause (t_list)); + } else if (not_clause((Node*)qual)) + return (push_nots(get_notclausearg (qual))); + else + return (qual); +} + +/* + * push-nots-- + * Negate the descendants of a 'not' clause. + * + * Returns the modified qualification. + * + */ +static Expr * +push_nots(Expr *qual) +{ + if (qual==NULL) + return (NULL); + + /* + * 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). + */ + if (is_opclause((Node*)qual)) { + Oper *oper = (Oper*)((Expr*)qual)->oper; + Oid negator = get_negator(oper->opno); + + if(negator) { + Oper *op = (Oper*) makeOper(negator, + InvalidOid, + oper->opresulttype, + 0, NULL); + op->op_fcache = (FunctionCache *) NULL; + return + (make_opclause(op, get_leftop(qual), get_rightop(qual))); + } 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., continue negating down through the clause's descendants. + */ + List *temp = NIL; + List *t_list = NIL; + + foreach(temp, qual->args) { + t_list = lappend(t_list,push_nots(lfirst(temp))); + } + return (make_orclause (t_list)); + } else if (or_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach(temp, qual->args) { + t_list = lappend(t_list,push_nots(lfirst(temp))); + } + return (make_andclause (t_list)); + } else if (not_clause((Node*)qual)) + /* Another 'not' cancels this 'not', so eliminate the 'not' and + * stop negating this branch. + */ + return (find_nots (get_notclausearg (qual))); + else + /* We don't know how to negate anything else, place a 'not' at this + * level. + */ + return (make_notclause (qual)); +} + +/* + * normalize-- + * Given a qualification tree with the 'not's pushed down, convert it + * to a tree in CNF by repeatedly applying the rule: + * ("OR" A ("AND" B C)) => ("AND" ("OR" A B) ("OR" A C)) + * bottom-up. + * Note that 'or' clauses will always be turned into 'and' clauses. + * + * Returns the modified qualification. + * + */ +static Expr * +normalize(Expr *qual) +{ + if (qual==NULL) + return (NULL); + + if (is_opclause((Node*)qual)) { + Expr *expr = (Expr*)qual; + return (make_clause(expr->opType, expr->oper, + lcons(normalize((Expr*)get_leftop(qual)), + lcons(normalize((Expr*)get_rightop(qual)), + NIL)))); + } else if (and_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + + foreach (temp, qual->args) { + t_list = lappend(t_list,normalize(lfirst(temp))); + } + return (make_andclause (t_list)); + } else if (or_clause((Node*)qual)) { + /* XXX - let form, maybe incorrect */ + List *orlist = NIL; + List *temp = NIL; + bool has_andclause = FALSE; + + foreach(temp, qual->args) { + orlist = lappend(orlist,normalize(lfirst(temp))); + } + foreach (temp, orlist) { + if (and_clause (lfirst(temp))) { + has_andclause = TRUE; + break; + } + } + if (has_andclause == TRUE) + return (make_andclause(or_normalize(orlist))); + else + return (make_orclause(orlist)); + + } else if (not_clause((Node*)qual)) + return (make_notclause (normalize (get_notclausearg (qual)))); + else + return (qual); +} + +/* + * or-normalize-- + * Given a list of exprs which are 'or'ed together, distribute any + * 'and' clauses. + * + * Returns the modified list. + * + */ +static List * +or_normalize(List *orlist) +{ + List *distributable = NIL; + List *new_orlist = NIL; + List *temp = NIL; + + if (orlist==NIL) + return NIL; + + foreach(temp, orlist) { + if (and_clause(lfirst(temp))) + distributable = lfirst(temp); + } + if (distributable) + new_orlist = LispRemove(distributable,orlist); + + if(new_orlist) { + return + (or_normalize(lcons(distribute_args(lfirst(new_orlist), + ((Expr*)distributable)->args), + lnext(new_orlist)))); + }else { + return (orlist); + } +} + +/* + * distribute-args-- + * Create new 'or' clauses by or'ing 'item' with each element of 'args'. + * E.g.: (distribute-args A ("AND" B C)) => ("AND" ("OR" A B) ("OR" A C)) + * + * Returns an 'and' clause. + * + */ +static List * +distribute_args(List *item, List *args) +{ + List *or_list = NIL; + List *n_list = NIL; + List *temp = NIL; + List *t_list = NIL; + + if (args==NULL) + return (item); + + foreach (temp,args) { + n_list = or_normalize(pull_ors(lcons(item, + lcons(lfirst(temp),NIL)))); + or_list = (List*)make_orclause(n_list); + t_list = lappend(t_list,or_list); + } + return ((List*)make_andclause(t_list)); +} + +/* + * qualcleanup-- + * Fix up a qualification by removing duplicate entries (left over from + * normalization), and by removing 'and' and 'or' clauses which have only + * one valid expr (e.g., ("AND" A) => A). + * + * Returns the modified qualfication. + * + */ +static List * +qualcleanup(Expr *qual) +{ + if (qual==NULL) + return (NIL); + + if (is_opclause((Node*)qual)) { + return ((List*)make_clause(qual->opType, qual->oper, + lcons(qualcleanup((Expr*)get_leftop(qual)), + lcons(qualcleanup((Expr*)get_rightop(qual)), + NIL)))); + } else if (and_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + List *new_and_args = NIL; + + foreach(temp, qual->args) + t_list = lappend(t_list,qualcleanup(lfirst(temp))); + + new_and_args = remove_duplicates(t_list); + + if(length (new_and_args) > 1) + return ((List*)make_andclause(new_and_args)); + else + return (lfirst(new_and_args)); + } + else if (or_clause((Node*)qual)) { + List *temp = NIL; + List *t_list = NIL; + List *new_or_args = NIL; + + foreach (temp, qual->args) + t_list = lappend(t_list,qualcleanup(lfirst(temp))); + + new_or_args = remove_duplicates(t_list); + + + if(length (new_or_args) > 1) + return ((List*)make_orclause (new_or_args)); + else + return (lfirst (new_or_args)); + } else if (not_clause((Node*)qual)) + return ((List*)make_notclause((Expr*)qualcleanup((Expr*)get_notclausearg(qual)))); + + else + return ((List*)qual); +} + +/* + * remove-ands-- + * Remove the explicit "AND"s from the qualification: + * ("AND" A B) => (A B) + * + * RETURNS : qual + * MODIFIES: qual + */ +static List * +remove_ands(Expr *qual) +{ + List *t_list = NIL; + + if (qual==NULL) + return (NIL); + if (is_opclause((Node*)qual)) { + return ((List*)make_clause(qual->opType, qual->oper, + lcons(remove_ands((Expr*)get_leftop(qual)), + lcons(remove_ands((Expr*)get_rightop(qual)), + NIL)))); + } else if (and_clause((Node*)qual)) { + List *temp = NIL; + foreach (temp, qual->args) + t_list = lappend(t_list,remove_ands(lfirst(temp))); + return(t_list); + } else if (or_clause((Node*)qual)) { + List *temp = NIL; + foreach (temp, qual->args) + t_list = lappend(t_list,remove_ands(lfirst(temp))); + return ((List*)make_orclause((List*)t_list)); + } else if (not_clause((Node*)qual)) { + return ((List*)make_notclause((Expr*)remove_ands((Expr*)get_notclausearg (qual)))); + } else { + return ((List*)qual); + } +} + +/***************************************************************************** + * + * EXISTENTIAL QUALIFICATIONS + * + *****************************************************************************/ + +/* + * update-relations-- + * Returns the range table indices (i.e., varnos) for all relations which + * are referenced in the target list. + * + */ +#if 0 +static List * +update_relations(List *tlist) +{ + return(NIL); +} +#endif + +/***************************************************************************** + * + * + * + *****************************************************************************/ + +static List * +remove_duplicates(List *list) +{ + List *i; + List *j; + List *result = NIL; + bool there_exists_duplicate = false; + + if (length(list) == 1) + return(list); + + foreach (i, list) { + if (i != NIL) { + foreach (j, lnext(i)) { + if (equal(lfirst(i), lfirst(j))) + there_exists_duplicate = true; + } + if (!there_exists_duplicate) + result = lappend(result, lfirst(i)); + + there_exists_duplicate = false; + } + } + return(result); +} diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c new file mode 100644 index 00000000000..fe1c2c92ba2 --- /dev/null +++ b/src/backend/optimizer/prep/preptlist.c @@ -0,0 +1,322 @@ +/*------------------------------------------------------------------------- + * + * preptlist.c-- + * Routines to preprocess the parse tree target list + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" +#include "nodes/parsenodes.h" + +#include "nodes/makefuncs.h" + +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/palloc.h" + +#include "parser/parsetree.h" /* for getrelid() */ +#include "parser/catalog_utils.h" + +#include "optimizer/internal.h" +#include "optimizer/prep.h" +#include "optimizer/clauses.h" +#include "optimizer/tlist.h" + +static List *expand_targetlist(List *tlist, Oid relid, int command_type, + Index result_relation); +static List *replace_matching_resname(List *new_tlist, + List *old_tlist); +static List *new_relation_targetlist(Oid relid, Index rt_index, + NodeTag node_type); + + +/* + * preprocess-targetlist-- + * Driver for preprocessing the parse tree targetlist. + * + * 1. Deal with appends and replaces by filling missing attributes + * in the target list. + * 2. Reset operator OIDs to the appropriate regproc ids. + * + * Returns the new targetlist. + */ +List * +preprocess_targetlist(List *tlist, + int command_type, + Index result_relation, + List *range_table) +{ + List *expanded_tlist = NIL; + Oid relid = InvalidOid; + List *t_list = NIL; + List *temp = NIL; + + if (result_relation>=1 && command_type != CMD_SELECT) { + relid = getrelid(result_relation, range_table); + } + + /* + * for heap_formtuple to work, the targetlist must match the exact + * order of the attributes. We also need to fill in the missing + * attributes here. -ay 10/94 + */ + expanded_tlist = + expand_targetlist(tlist, relid, command_type, result_relation); + + /* XXX should the fix-opids be this early?? */ + /* was mapCAR */ + foreach (temp,expanded_tlist) { + TargetEntry *tle = lfirst(temp); + if (tle->expr) + fix_opid(tle->expr); + } + t_list = copyObject(expanded_tlist); + + /* ------------------ + * for "replace" or "delete" queries, add ctid of the result + * relation into the target list so that the ctid can get + * propogate through the execution and in the end ExecReplace() + * will find the right tuple to replace or delete. This + * extra field will be removed in ExecReplace(). + * For convinient, we append this extra field to the end of + * the target list. + * ------------------ + */ + if (command_type == CMD_UPDATE || command_type == CMD_DELETE) { + TargetEntry *ctid; + Resdom *resdom; + Var *var; + + resdom = makeResdom(length(t_list) + 1, + 27, + 6, + "ctid", + 0, + 0, + 1); + + var = makeVar(result_relation, -1, 27, result_relation, -1); + + ctid = makeNode(TargetEntry); + ctid->resdom = resdom; + ctid->expr = (Node *)var; + t_list = lappend(t_list, ctid); + } + + return(t_list); +} + +/***************************************************************************** + * + * TARGETLIST EXPANSION + * + *****************************************************************************/ + +/* + * expand-targetlist-- + * Given a target list as generated by the parser and a result relation, + * add targetlist entries for the attributes which have not been used. + * + * XXX This code is only supposed to work with unnested relations. + * + * 'tlist' is the original target list + * 'relid' is the relid of the result relation + * 'command' is the update command + * + * Returns the expanded target list, sorted in resno order. + */ +static List * +expand_targetlist(List *tlist, + Oid relid, + int command_type, + Index result_relation) +{ + NodeTag node_type = T_Invalid; + + switch (command_type) { + case CMD_INSERT: + node_type = (NodeTag)T_Const; + break; + case CMD_UPDATE: + node_type = (NodeTag)T_Var; + break; + } + + if(node_type != T_Invalid) { + List *ntlist = new_relation_targetlist(relid, + result_relation, + node_type); + + return (replace_matching_resname(ntlist, tlist)); + } else { + return (tlist); + } + +} + + +static List * +replace_matching_resname(List *new_tlist, List *old_tlist) +{ + List *temp, *i; + List *t_list = NIL; + + foreach (i,new_tlist) { + TargetEntry *new_tle = (TargetEntry *)lfirst(i); + TargetEntry *matching_old_tl = NULL; + + foreach (temp, old_tlist) { + TargetEntry *old_tle = (TargetEntry *)lfirst(temp); + + old_tle = lfirst(temp); + if (!strcmp(old_tle->resdom->resname, + new_tle->resdom->resname)) { + matching_old_tl = old_tle; + break; + } + } + + if(matching_old_tl) { + matching_old_tl->resdom->resno = + new_tle->resdom->resno; + t_list = lappend(t_list, matching_old_tl); + } + else { + t_list = lappend(t_list, new_tle); + } + } + + /* + * It is possible that 'old_tlist' has some negative + * attributes (i.e. negative resnos). This only happens + * if this is a replace/append command and we explicitly + * specify a system attribute. Of course this is not a very good + * idea if this is a user query, but on the other hand the rule + * manager uses this mechanism to replace rule locks. + * + * So, copy all these entries to the end of the target list + * and set their 'resjunk' value to 1 to show that these are + * special attributes and have to be treated specially by the + * executor! + */ + foreach (temp, old_tlist) { + TargetEntry *old_tle, *new_tl; + Resdom *newresno; + + old_tle = lfirst(temp); + if (old_tle->resdom->resno < 0) { + newresno = (Resdom*) copyObject((Node*)old_tle->resdom); + newresno->resno = length(t_list) +1; + newresno->resjunk = 1; + new_tl = MakeTLE(newresno, old_tle->expr); + t_list = lappend(t_list, new_tl); + } + } + + return (t_list); +} + +/* + * new-relation-targetlist-- + * Generate a targetlist for the relation with relation OID 'relid' + * and rangetable index 'rt-index'. + * + * Returns the new targetlist. + */ +static List * +new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type) +{ + AttrNumber attno; + List *t_list = NIL; + char *attname; + Oid atttype = 0; + int16 typlen = 0; + bool attisset = false; +/* Oid type_id; */ +/* type_id = RelationIdGetTypeId(relid); */ + + for(attno=1; attno <= get_relnatts(relid); attno++) { + attname = get_attname(/*type_id,*/ relid, attno); + atttype = get_atttype(/*type_id,*/ relid, attno); + /* + * Since this is an append or replace, the size of any set + * attribute is the size of the OID used to represent it. + */ + attisset = get_attisset(/* type_id,*/ relid, attname); + if (attisset) { + typlen = tlen(type("oid")); + } else { + typlen = get_typlen(atttype); + } + + switch (node_type) { + case T_Const: + { + struct varlena *typedefault = get_typdefault(atttype); + int temp = 0; + Const *temp2 = (Const*)NULL; + TargetEntry *temp3 = (TargetEntry *)NULL; + + if (typedefault==NULL) + temp = 0; + else + temp = typlen; + + temp2 = makeConst (atttype, + temp, + (Datum)typedefault, + (typedefault == (struct varlena *)NULL), + /* XXX this is bullshit */ + false, + false /* not a set */); + + temp3 = MakeTLE (makeResdom(attno, + atttype, + typlen, + attname, + 0, + (Oid)0, + 0), + (Node*)temp2); + t_list = lappend(t_list,temp3); + break; + } + case T_Var: + { + Var *temp_var = (Var*)NULL; + TargetEntry *temp_list = NULL; + + temp_var = + makeVar(rt_index, attno, atttype, rt_index, attno); + + temp_list = MakeTLE(makeResdom(attno, + atttype, + typlen, + attname, + 0, + (Oid)0, + 0), + (Node*)temp_var); + t_list = lappend(t_list,temp_list); + break; + } + default: /* do nothing */ + break; + } + } + + return(t_list); +} + + diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c new file mode 100644 index 00000000000..8e1491940db --- /dev/null +++ b/src/backend/optimizer/prep/prepunion.c @@ -0,0 +1,400 @@ +/*------------------------------------------------------------------------- + * + * prepunion.c-- + * Routines to plan archive, inheritance, union, and version queries + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/nodes.h" +#include "nodes/pg_list.h" +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" +#include "nodes/relation.h" + +#include "parser/parse_query.h" +#include "parser/parsetree.h" + +#include "utils/elog.h" +#include "utils/lsyscache.h" + +#include "optimizer/internal.h" +#include "optimizer/prep.h" +#include "optimizer/plancat.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" + +static List *plan_union_query(List *relids, Index rt_index, + RangeTblEntry *rt_entry, Query *parse, UnionFlag flag, + List **union_rtentriesPtr); +static RangeTblEntry *new_rangetable_entry(Oid new_relid, + RangeTblEntry *old_entry); +static Query *subst_rangetable(Query *root, Index index, + RangeTblEntry *new_entry); +static void fix_parsetree_attnums(Index rt_index, Oid old_relid, + Oid new_relid, Query *parsetree); +static Append *make_append(List *unionplans, Index rt_index, + List *union_rt_entries, List *tlist); + + +/* + * find-all-inheritors - + * Returns a list of relids corresponding to relations that inherit + * attributes from any relations listed in either of the argument relid + * lists. + */ +List * +find_all_inheritors(List *unexamined_relids, + List *examined_relids) +{ + List *new_inheritors = NIL; + List *new_examined_relids = NIL; + List *new_unexamined_relids = NIL; + + /* Find all relations which inherit from members of + * 'unexamined-relids' and store them in 'new-inheritors'. + */ + List *rels = NIL; + List *newrels = NIL; + + foreach(rels,unexamined_relids) { + newrels = (List*)LispUnioni(find_inheritance_children(lfirsti(rels)), + newrels); + } + new_inheritors = newrels; + + new_examined_relids = (List*)LispUnioni(examined_relids,unexamined_relids); + new_unexamined_relids = set_differencei(new_inheritors, + new_examined_relids); + + if (new_unexamined_relids==NULL) { + return(new_examined_relids); + } else { + return (find_all_inheritors (new_unexamined_relids, + new_examined_relids)); + } +} + +/* + * first-matching-rt-entry - + * Given a rangetable, find the first rangetable entry that represents + * the appropriate special case. + * + * Returns a rangetable index., Returns -1 if no matches + */ +int +first_matching_rt_entry (List *rangetable, UnionFlag flag) +{ + int count = 0; + List *temp = NIL; + + foreach(temp, rangetable) { + RangeTblEntry *rt_entry = lfirst(temp); + + switch(flag) { + case INHERITS_FLAG: + if (rt_entry->inh) + return count+1; + break; + case ARCHIVE_FLAG: + if (rt_entry->archive) + return count+1; + break; + default: + break; + } + count++; + } + + return(-1); +} + + +/* + * plan-union-queries-- + * + * Plans the queries for a given parent relation. + * + * Returns a list containing a list of plans and a list of rangetable + * entries to be inserted into an APPEND node. + * XXX - what exactly does this mean, look for make_append + */ +Append * +plan_union_queries(Index rt_index, + Query *parse, + UnionFlag flag) +{ + List *rangetable = parse->rtable; + RangeTblEntry *rt_entry = rt_fetch(rt_index,rangetable); + List *union_relids = NIL; + List *union_plans = NIL; + List *union_rt_entries = NIL; + + switch (flag) { + case INHERITS_FLAG: + union_relids = + find_all_inheritors(lconsi(rt_entry->relid, + NIL), + NIL); + break; + +#if 0 + case UNION_FLAG: + { + Index rt_index = 0; + union_plans = handleunion(root,rangetable,tlist,qual); + return (make_append (union_plans, + rt_index, rangetable, + ((Plan*)lfirst(union_plans))->targetlist )); + } + break; +#endif + + case VERSION_FLAG: + union_relids = VersionGetParents(rt_entry->relid); + break; + + case ARCHIVE_FLAG: + union_relids = find_archive_rels(rt_entry->relid); + break; + + default: + /* do nothing */ + break; + } + + /* + * Remove the flag for this relation, since we're about to handle it + * (do it before recursing!). + * XXX destructive parse tree change + */ + switch(flag) { + case INHERITS_FLAG: + rt_fetch(rt_index,rangetable)->inh = false; + break; + case ARCHIVE_FLAG: + rt_fetch(rt_index,rangetable)->archive = false; + break; + default: + break; + } + + /* XXX - can't find any reason to sort union-relids + * as paul did, so we're leaving it out for now + * (maybe forever) - jeff & lp + * + * [maybe so. btw, jeff & lp did the lisp conversion, according to Paul. + * -- ay 10/94.] + */ + union_plans = plan_union_query(union_relids, rt_index, rt_entry, + parse, flag, &union_rt_entries); + + return (make_append(union_plans, + rt_index, + union_rt_entries, + ((Plan*)lfirst(union_plans))->targetlist)); +} + + +/* + * plan-union-query-- + * Returns a list of plans for 'relids' and a list of range table entries + * in union_rtentries. + */ +static List * +plan_union_query(List *relids, + Index rt_index, + RangeTblEntry *rt_entry, + Query *root, + UnionFlag flag, + List **union_rtentriesPtr) +{ + List *i; + List *union_plans = NIL; + List *union_rtentries = NIL; + + foreach (i, relids) { + int relid = lfirsti(i); + RangeTblEntry *new_rt_entry = new_rangetable_entry(relid, + rt_entry); + Query *new_root = subst_rangetable(root, + rt_index, + new_rt_entry); + + /* reset the uniqueflag and sortclause in parse tree root, so that + * sorting will only be done once after append + */ +/* new_root->uniqueFlag = false; */ + new_root->uniqueFlag = NULL; + new_root->sortClause = NULL; + if (flag == ARCHIVE_FLAG) { + /* + * the entire union query uses the same (most recent) schema. + * to do otherwise would require either ragged tuples or careful + * archiving and interpretation of pg_attribute... + */ + } else { + fix_parsetree_attnums(rt_index, + rt_entry->relid, + relid, + new_root); + } + + union_plans = lappend(union_plans, planner(new_root)); + union_rtentries = lappend(union_rtentries, new_rt_entry); + } + + *union_rtentriesPtr = union_rtentries; + return(union_plans); +} + +/* + * new-rangetable-entry - + * Replaces the name and relid of 'old-entry' with the values for + * 'new-relid'. + * + * Returns a copy of 'old-entry' with the parameters substituted. + */ +static RangeTblEntry * +new_rangetable_entry(Oid new_relid, RangeTblEntry *old_entry) +{ + RangeTblEntry *new_entry = copyObject(old_entry); + + /* ??? someone tell me what the following is doing! - ay 11/94 */ + if (!strcmp(new_entry->refname, "*CURRENT*") || + !strcmp(new_entry->refname, "*NEW*")) + new_entry->refname = get_rel_name(new_relid); + else + new_entry->relname = get_rel_name(new_relid); + + new_entry->relid = new_relid; + return(new_entry); +} + +/* + * subst-rangetable-- + * Replaces the 'index'th rangetable entry in 'root' with 'new-entry'. + * + * Returns a new copy of 'root'. + */ +static Query * +subst_rangetable(Query *root, Index index, RangeTblEntry *new_entry) +{ + Query *new_root = copyObject(root); + List *temp = NIL; + int i = 0; + + for(temp = new_root->rtable,i =1; i < index; temp =lnext(temp),i++) + ; + lfirst(temp) = new_entry; + + return (new_root); +} + +static void +fix_parsetree_attnums_nodes(Index rt_index, + Oid old_relid, + Oid new_relid, + Node *node) +{ + if (node==NULL) + return; + + switch(nodeTag(node)) { + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *)node; + + fix_parsetree_attnums_nodes(rt_index, old_relid, new_relid, + tle->expr); + } + break; + case T_Expr: + { + Expr *expr = (Expr *)node; + fix_parsetree_attnums_nodes(rt_index, old_relid, new_relid, + (Node*)expr->args); + } + break; + case T_Var: + { + Var *var = (Var *)node; + Oid old_typeid, new_typeid; + +/* old_typeid = RelationIdGetTypeId(old_relid);*/ +/* new_typeid = RelationIdGetTypeId(new_relid);*/ + old_typeid = old_relid; + new_typeid = new_relid; + + if (var->varno == rt_index && var->varattno != 0) { + var->varattno = + get_attnum(new_typeid, + get_attname(old_typeid, var->varattno)); + } + } + break; + case T_List: + { + List *l; + foreach(l, (List*)node) { + fix_parsetree_attnums_nodes(rt_index, old_relid, new_relid, + (Node*)lfirst(l)); + } + } + break; + default: + break; + } +} + +/* + * fix-parsetree-attnums-- + * Replaces attribute numbers from the relation represented by + * 'old-relid' in 'parsetree' with the attribute numbers from + * 'new-relid'. + * + * Returns the destructively-modified parsetree. + * + */ +static void +fix_parsetree_attnums(Index rt_index, + Oid old_relid, + Oid new_relid, + Query *parsetree) +{ + if (old_relid == new_relid) + return; + + fix_parsetree_attnums_nodes(rt_index, old_relid, new_relid, + (Node*)parsetree->targetList); + fix_parsetree_attnums_nodes(rt_index, old_relid, new_relid, + parsetree->qual); +} + +static Append * +make_append(List *unionplans, + Index rt_index, + List *union_rt_entries, + List *tlist) +{ + Append *node = makeNode(Append); + + node->unionplans = unionplans; + node->unionrelid = rt_index; + node->unionrtentries = union_rt_entries; + node->plan.cost = 0.0; + node->plan.state = (EState*)NULL; + node->plan.targetlist = tlist; + node->plan.qual = NIL; + node->plan.lefttree = (Plan*)NULL; + node->plan.righttree = (Plan*)NULL; + + return(node); +} diff --git a/src/backend/optimizer/tlist.h b/src/backend/optimizer/tlist.h new file mode 100644 index 00000000000..8906460de91 --- /dev/null +++ b/src/backend/optimizer/tlist.h @@ -0,0 +1,36 @@ +/*------------------------------------------------------------------------- + * + * tlist.h-- + * prototypes for tlist.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: tlist.h,v 1.1.1.1 1996/07/09 06:21:34 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef TLIST_H +#define TLIST_H + +extern int exec_tlist_length(List *targelist); +extern TargetEntry *tlistentry_member(Var *var, List *targetlist); +extern Expr *matching_tlvar(Var *var, List *targetlist); +extern void add_tl_element(Rel *rel, Var *var); +extern TargetEntry *create_tl_element(Var *var, int resdomno); +extern List *get_actual_tlist(List *tlist); +extern Resdom *tlist_member(Var *var, List *tlist); +extern Resdom *tlist_resdom(List *tlist, Resdom *resnode); + +extern TargetEntry *MakeTLE(Resdom *resdom, Node *expr); +extern Var *get_expr(TargetEntry *tle); + +extern TargetEntry *match_varid(Var *test_var, List *tlist); +extern List *new_unsorted_tlist(List *targetlist); +extern List *copy_vars(List *target, List *source); +extern List *flatten_tlist(List *tlist); +extern List *flatten_tlist_vars(List *full_tlist, + List *flat_tlist); +extern void AddGroupAttrToTlist(List *tlist, List *grpCl); + +#endif /* TLIST_H */ diff --git a/src/backend/optimizer/util/Makefile.inc b/src/backend/optimizer/util/Makefile.inc new file mode 100644 index 00000000000..18955d282c8 --- /dev/null +++ b/src/backend/optimizer/util/Makefile.inc @@ -0,0 +1,15 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for optimizer/util +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= clauseinfo.c clauses.c indexnode.c internal.c plancat.c \ + joininfo.c keys.c ordering.c pathnode.c relnode.c tlist.c var.c diff --git a/src/backend/optimizer/util/clauseinfo.c b/src/backend/optimizer/util/clauseinfo.c new file mode 100644 index 00000000000..1ab747ee176 --- /dev/null +++ b/src/backend/optimizer/util/clauseinfo.c @@ -0,0 +1,187 @@ +/*------------------------------------------------------------------------- + * + * clauseinfo.c-- + * ClauseInfo node manipulation routines. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/clauseinfo.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/relation.h" +#include "nodes/nodeFuncs.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" + +/* + * valid-or-clause-- + * + * Returns t iff the clauseinfo node contains a 'normal' 'or' clause. + * + */ +bool +valid_or_clause(CInfo *clauseinfo) +{ + if (clauseinfo != NULL && + !single_node((Node*)clauseinfo->clause) && + !clauseinfo->notclause && + or_clause((Node*)clauseinfo->clause)) + return(true); + else + return(false); +} + +/* + * get-actual-clauses-- + * + * Returns a list containing the clauses from 'clauseinfo-list'. + * + */ +List * +get_actual_clauses(List *clauseinfo_list) +{ + List *temp = NIL; + List *result = NIL; + CInfo *clause = (CInfo *)NULL; + + foreach(temp,clauseinfo_list) { + clause = (CInfo *)lfirst(temp); + result = lappend(result,clause->clause); + } + return(result); +} + +/* + * XXX NOTE: + * The following routines must return their contents in the same order + * (e.g., the first clause's info should be first, and so on) or else + * get_index_sel() won't work. + * + */ + +/* + * get_relattvals-- + * For each member of a list of clauseinfo nodes to be used with an + * index, create a vectori-long specifying: + * the attnos, + * the values of the clause constants, and + * flags indicating the type and location of the constant within + * each clause. + * Each clause is of the form (op var some_type_of_constant), thus the + * flag indicating whether the constant is on the left or right should + * always be *SELEC-CONSTANT-RIGHT*. + * + * 'clauseinfo-list' is a list of clauseinfo nodes + * + * Returns a list of vectori-longs. + * + */ +void +get_relattvals(List *clauseinfo_list, + List **attnos, + List **values, + List **flags) +{ + List *result1 = NIL; + List *result2 = NIL; + List *result3 = NIL; + CInfo *temp = (CInfo *)NULL; + List *i = NIL; + + foreach (i,clauseinfo_list) { + int dummy; + AttrNumber attno; + Datum constval; + int flag; + + temp = (CInfo *)lfirst(i); + get_relattval((Node*)temp->clause, &dummy, &attno, &constval, &flag); + result1 = lappendi(result1, attno); + result2 = lappendi(result2, constval); + result3 = lappendi(result3, flag); + } + + *attnos = result1; + *values = result2; + *flags = result3; + return; +} + +/* + * get_joinvars -- + * Given a list of join clauseinfo nodes to be used with the index + * of an inner join relation, return three lists consisting of: + * the attributes corresponding to the inner join relation + * the value of the inner var clause (always "") + * whether the attribute appears on the left or right side of + * the operator. + * + * 'relid' is the inner join relation + * 'clauseinfo-list' is a list of qualification clauses to be used with + * 'rel' + * + */ +void +get_joinvars(Oid relid, + List *clauseinfo_list, + List **attnos, + List **values, + List **flags) +{ + List *result1 = NIL; + List *result2 = NIL; + List *result3 = NIL; + List *temp; + + foreach(temp, clauseinfo_list) { + CInfo *clauseinfo = lfirst(temp); + Expr *clause = clauseinfo->clause; + + if( IsA (get_leftop(clause),Var) && + (relid == (get_leftop(clause))->varno)) { + + result1 = lappendi(result1, (get_leftop(clause))->varattno); + result2 = lappend(result2, ""); + result3 = lappendi(result3, _SELEC_CONSTANT_RIGHT_); + } else { + result1 = lappendi(result1, (get_rightop(clause))->varattno); + result2 = lappend(result2, ""); + result3 = lappendi(result3, _SELEC_CONSTANT_LEFT_); + } + } + *attnos = result1; + *values = result2; + *flags = result3; + return; +} + +/* + * get_opnos-- + * Create and return a list containing the clause operators of each member + * of a list of clauseinfo nodes to be used with an index. + * + */ +List * +get_opnos(List *clauseinfo_list) +{ + CInfo *temp = (CInfo *)NULL; + List *result = NIL; + List *i = NIL; + + foreach(i,clauseinfo_list) { + temp = (CInfo *)lfirst(i); + result = + lappendi(result, + (((Oper*)temp->clause->oper)->opno)); + } + return(result); +} + + diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c new file mode 100644 index 00000000000..daba4d8fdb0 --- /dev/null +++ b/src/backend/optimizer/util/clauses.c @@ -0,0 +1,736 @@ +/*------------------------------------------------------------------------- + * + * clauses.c-- + * routines to manipulate qualification clauses + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + * HISTORY + * AUTHOR DATE MAJOR EVENT + * Andrew Yu Nov 3, 1994 clause.c and clauses.c combined + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "nodes/parsenodes.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" + +#include "catalog/pg_aggregate.h" + +#include "utils/elog.h" +#include "utils/syscache.h" +#include "utils/lsyscache.h" + +#include "optimizer/clauses.h" +#include "optimizer/internal.h" +#include "optimizer/var.h" + + +Expr * +make_clause(int type, Node *oper, List *args) +{ + if (type == AND_EXPR || type == OR_EXPR || type == NOT_EXPR || + type == OP_EXPR || type == FUNC_EXPR) { + Expr *expr = makeNode(Expr); + + /* + * assume type checking already done and we don't need the type of + * the expr any more. + */ + expr->typeOid = InvalidOid; + expr->opType = type; + expr->oper = oper; /* ignored for AND, OR, NOT */ + expr->args = args; + return expr; + }else { + /* will this ever happen? translated from lispy C code - ay 10/94 */ + return((Expr*)args); + } +} + + +/***************************************************************************** + * OPERATOR clause functions + *****************************************************************************/ + + +/* + * is_opclause-- + * + * Returns t iff the clause is an operator clause: + * (op expr expr) or (op expr). + * + * [historical note: is_clause has the exact functionality and is used + * throughout the code. They're renamed to is_opclause for clarity. + * - ay 10/94.] + */ +bool +is_opclause(Node *clause) +{ + return + (clause!=NULL && + nodeTag(clause)==T_Expr && ((Expr*)clause)->opType==OP_EXPR); +} + +/* + * make_opclause-- + * Creates a clause given its operator left operand and right + * operand (if it is non-null). + * + */ +Expr * +make_opclause(Oper *op, Var *leftop, Var *rightop) +{ + Expr *expr = makeNode(Expr); + + expr->typeOid = InvalidOid; /* assume type checking done */ + expr->opType = OP_EXPR; + expr->oper = (Node*)op; + expr->args = makeList(leftop, rightop, -1); + return expr; +} + +/* + * get_leftop-- + * + * Returns the left operand of a clause of the form (op expr expr) + * or (op expr) + * NB: it is assumed (for now) that all expr must be Var nodes + */ +Var * +get_leftop(Expr *clause) +{ + if (clause->args!=NULL) + return(lfirst(clause->args)); + else + return NULL; +} + +/* + * get_rightop + * + * Returns the right operand in a clause of the form (op expr expr). + * + */ +Var * +get_rightop(Expr *clause) +{ + if (clause->args!=NULL && lnext(clause->args)!=NULL) + return (lfirst(lnext(clause->args))); + else + return NULL; +} + +/***************************************************************************** + * AGG clause functions + *****************************************************************************/ + +bool +agg_clause(Node *clause) +{ + return + (clause!=NULL && nodeTag(clause)==T_Aggreg); +} + +/***************************************************************************** + * FUNC clause functions + *****************************************************************************/ + +/* + * is_funcclause-- + * + * Returns t iff the clause is a function clause: (func { expr }). + * + */ +bool +is_funcclause(Node *clause) +{ + return + (clause!=NULL && + nodeTag(clause)==T_Expr && ((Expr*)clause)->opType==FUNC_EXPR); +} + +/* + * make_funcclause-- + * + * Creates a function clause given the FUNC node and the functional + * arguments. + * + */ +Expr * +make_funcclause(Func *func, List *funcargs) +{ + Expr *expr = makeNode(Expr); + + expr->typeOid = InvalidOid; /* assume type checking done */ + expr->opType = FUNC_EXPR; + expr->oper = (Node*)func; + expr->args = funcargs; + return expr; +} + +/***************************************************************************** + * OR clause functions + *****************************************************************************/ + +/* + * or_clause-- + * + * Returns t iff the clause is an 'or' clause: (OR { expr }). + * + */ +bool +or_clause(Node *clause) +{ + return + (clause!=NULL && + nodeTag(clause)==T_Expr && ((Expr*)clause)->opType==OR_EXPR); +} + +/* + * make_orclause-- + * + * Creates an 'or' clause given a list of its subclauses. + * + */ +Expr * +make_orclause(List *orclauses) +{ + Expr *expr = makeNode(Expr); + + expr->typeOid = InvalidOid; /* assume type checking done */ + expr->opType = OR_EXPR; + expr->oper = NULL; + expr->args = orclauses; + return expr; +} + +/***************************************************************************** + * NOT clause functions + *****************************************************************************/ + +/* + * not_clause-- + * + * Returns t iff this is a 'not' clause: (NOT expr). + * + */ +bool +not_clause(Node *clause) +{ + return + (clause!=NULL && + nodeTag(clause)==T_Expr && ((Expr*)clause)->opType == NOT_EXPR); +} + +/* + * make_notclause-- + * + * Create a 'not' clause given the expression to be negated. + * + */ +Expr * +make_notclause(Expr *notclause) +{ + Expr *expr = makeNode(Expr); + + expr->typeOid = InvalidOid; /* assume type checking done */ + expr->opType = NOT_EXPR; + expr->oper = NULL; + expr->args = lcons(notclause, NIL); + return expr; +} + +/* + * get_notclausearg-- + * + * Retrieve the clause within a 'not' clause + * + */ +Expr * +get_notclausearg(Expr *notclause) +{ + return(lfirst(notclause->args)); +} + +/***************************************************************************** + * AND clause functions + *****************************************************************************/ + + +/* + * and_clause-- + * + * Returns t iff its argument is an 'and' clause: (AND { expr }). + * + */ +bool +and_clause(Node *clause) +{ + return + (clause!=NULL && + nodeTag(clause)==T_Expr && ((Expr*)clause)->opType == AND_EXPR); +} +/* + * make_andclause-- + * + * Create an 'and' clause given its arguments in a list. + * + */ +Expr * +make_andclause(List *andclauses) +{ + Expr *expr = makeNode(Expr); + + expr->typeOid = InvalidOid; /* assume type checking done */ + expr->opType = AND_EXPR; + expr->oper = NULL; + expr->args = andclauses; + return expr; +} + +/***************************************************************************** + * * + * * + * * + *****************************************************************************/ + + +/* + * pull-constant-clauses-- + * Scans through a list of qualifications and find those that + * contain no variables. + * + * Returns a list of the constant clauses in constantQual and the remaining + * quals as the return value. + * + */ +List * +pull_constant_clauses(List *quals, List **constantQual) +{ + List *q; + List *constqual=NIL; + List *restqual=NIL; + + foreach(q, quals) { + if (!contain_var_clause(lfirst(q))) { + constqual = lcons(lfirst(q), constqual); + }else { + restqual = lcons(lfirst(q), restqual); + } + } + freeList(quals); + *constantQual = constqual; + return restqual; +} + +/* + * clause-relids-vars-- + * Retrieves relids and vars appearing within a clause. + * Returns ((relid1 relid2 ... relidn) (var1 var2 ... varm)) where + * vars appear in the clause this is done by recursively searching + * through the left and right operands of a clause. + * + * Returns the list of relids and vars. + * + * XXX take the nreverse's out later + * + */ +void +clause_relids_vars(Node *clause, List **relids, List **vars) +{ + List *clvars = pull_var_clause(clause); + List *var_list = NIL; + List *varno_list = NIL; + List *i = NIL; + + foreach (i, clvars) { + Var *var = (Var *)lfirst(i); + + if (!intMember(var->varno, varno_list)) { + varno_list = lappendi(varno_list, var->varno); + var_list = lappend(var_list, var); + } + } + + *relids = varno_list; + *vars = var_list; + return; +} + +/* + * NumRelids-- + * (formerly clause-relids) + * + * Returns the number of different relations referenced in 'clause'. + */ +int +NumRelids(Node *clause) +{ + List *vars = pull_var_clause(clause); + List *i = NIL; + List *var_list = NIL; + + foreach (i, vars) { + Var *var = (Var *)lfirst(i); + + if (!intMember(var->varno, var_list)) { + var_list = lconsi(var->varno, var_list); + } + } + + return(length(var_list)); +} + +/* + * contains-not-- + * + * Returns t iff the clause is a 'not' clause or if any of the + * subclauses within an 'or' clause contain 'not's. + * + */ +bool +contains_not(Node *clause) +{ + if (single_node(clause)) + return (false); + + if (not_clause(clause)) + return (true); + + if (or_clause(clause)) { + List *a; + foreach(a, ((Expr*)clause)->args) { + if (contains_not(lfirst(a))) + return (true); + } + } + + return(false); +} + +/* + * join-clause-p-- + * + * Returns t iff 'clause' is a valid join clause. + * + */ +bool +join_clause_p(Node *clause) +{ + Node *leftop, *rightop; + + if (!is_opclause(clause)) + return false; + + leftop = (Node*)get_leftop((Expr*)clause); + rightop = (Node*)get_rightop((Expr*)clause); + + /* + * One side of the clause (i.e. left or right operands) + * must either be a var node ... + */ + if (IsA(leftop,Var) || IsA(rightop,Var)) + return true; + + /* + * ... or a func node. + */ + if (is_funcclause(leftop) || is_funcclause(rightop)) + return(true); + + return(false); +} + +/* + * qual-clause-p-- + * + * Returns t iff 'clause' is a valid qualification clause. + * + */ +bool +qual_clause_p(Node *clause) +{ + if (!is_opclause(clause)) + return false; + + if (IsA (get_leftop((Expr*)clause),Var) && + IsA (get_rightop((Expr*)clause),Const)) + { + return(true); + } + else if (IsA (get_rightop((Expr*)clause),Var) && + IsA (get_leftop((Expr*)clause),Const)) + { + return(true); + } + return(false); +} + +/* + * fix-opid-- + * Calculate the opfid from the opno... + * + * Returns nothing. + * + */ +void +fix_opid(Node *clause) +{ + if (clause==NULL || single_node(clause)) { + ; + } + else if (or_clause (clause)) { + fix_opids(((Expr*)clause)->args); + } + else if (is_funcclause (clause)) { + fix_opids(((Expr*)clause)->args); + } + else if (IsA(clause,ArrayRef)) { + ArrayRef *aref = (ArrayRef *)clause; + + fix_opids(aref->refupperindexpr); + fix_opids(aref->reflowerindexpr); + fix_opid(aref->refexpr); + fix_opid(aref->refassgnexpr); + } + else if (not_clause(clause)) { + fix_opid((Node*)get_notclausearg((Expr*)clause)); + } + else if (is_opclause (clause)) { + replace_opid((Oper*)((Expr*)clause)->oper); + fix_opid((Node*)get_leftop((Expr*)clause)); + fix_opid((Node*)get_rightop((Expr*)clause)); + } + +} + +/* + * fix-opids-- + * Calculate the opfid from the opno for all the clauses... + * + * Returns its argument. + * + */ +List * +fix_opids(List *clauses) +{ + List *clause; + + foreach(clause, clauses) + fix_opid(lfirst(clause)); + + return(clauses); +} + +/* + * get_relattval-- + * For a non-join clause, returns a list consisting of the + * relid, + * attno, + * value of the CONST node (if any), and a + * flag indicating whether the value appears on the left or right + * of the operator and whether the value varied. + * + * OLD OBSOLETE COMMENT FOLLOWS: + * If 'clause' is not of the format (op var node) or (op node var), + * or if the var refers to a nested attribute, then -1's are returned for + * everything but the value a blank string "" (pointer to \0) is + * returned for the value if it is unknown or null. + * END OF OLD OBSOLETE COMMENT. + * NEW COMMENT: + * when defining rules one of the attibutes of the operator can + * be a Param node (which is supposed to be treated as a constant). + * However as there is no value specified for a parameter until run time + * this routine used to return "" as value, which made 'compute_selec' + * to bomb (because it was expecting a lisp integer and got back a lisp + * string). Now the code returns a plain old good "lispInteger(0)". + * + */ +void +get_relattval(Node *clause, + int *relid, + AttrNumber *attno, + Datum *constval, + int *flag) +{ + Var *left = get_leftop((Expr*)clause); + Var *right = get_rightop((Expr*)clause); + + if(is_opclause(clause) && IsA(left,Var) && + IsA(right,Const)) { + + if(right!=NULL) { + + *relid = left->varno; + *attno = left->varattno; + *constval = ((Const *)right)->constvalue; + *flag = (_SELEC_CONSTANT_RIGHT_ | _SELEC_IS_CONSTANT_); + + } else { + + *relid = left->varno; + *attno = left->varattno; + *constval = 0; + *flag = (_SELEC_CONSTANT_RIGHT_ | _SELEC_NOT_CONSTANT_); + + } + }else if (is_opclause(clause) && + is_funcclause((Node*)left) && + IsA(right,Const)) { + List *args = ((Expr*)left)->args; + + + *relid = ((Var*)lfirst(args))->varno; + *attno = InvalidAttrNumber; + *constval = ((Const*)right)->constvalue; + *flag = (_SELEC_CONSTANT_RIGHT_ | _SELEC_IS_CONSTANT_); + + /* + * XXX both of these func clause handling if's seem wrong to me. + * they assume that the first argument is the Var. It could + * not handle (for example) f(1, emp.name). I think I may have + * been assuming no constants in functional index scans when I + * implemented this originally (still currently true). + * -mer 10 Aug 1992 + */ + } else if (is_opclause(clause) && + is_funcclause((Node*)right) && + IsA(left,Const)) { + List *args = ((Expr*)right)->args; + + *relid = ((Var*)lfirst(args))->varno; + *attno = InvalidAttrNumber; + *constval = ((Const*)left)->constvalue; + *flag = ( _SELEC_IS_CONSTANT_); + + } else if (is_opclause (clause) && IsA (right,Var) && + IsA (left,Const)) { + if (left!=NULL) { + + *relid = right->varno; + *attno = right->varattno; + *constval = ((Const*)left)->constvalue; + *flag = (_SELEC_IS_CONSTANT_); + } else { + + *relid = right->varno; + *attno = right->varattno; + *constval = 0; + *flag = (_SELEC_NOT_CONSTANT_); + } + } else { + /* One or more of the operands are expressions + * (e.g., oper clauses) + */ + *relid = _SELEC_VALUE_UNKNOWN_; + *attno = _SELEC_VALUE_UNKNOWN_; + *constval = 0; + *flag = (_SELEC_NOT_CONSTANT_); + } +} + +/* + * get_relsatts-- + * + * Returns a list + * ( relid1 attno1 relid2 attno2 ) + * for a joinclause. + * + * If the clause is not of the form (op var var) or if any of the vars + * refer to nested attributes, then -1's are returned. + * + */ +void +get_rels_atts(Node *clause, + int *relid1, + AttrNumber *attno1, + int *relid2, + AttrNumber *attno2) +{ + Var *left = get_leftop((Expr*)clause); + Var *right = get_rightop((Expr*)clause); + bool var_left = (IsA(left,Var)); + bool var_right = (IsA(right,Var)); + bool varexpr_left = (bool)((IsA(left,Func) || IsA (left,Oper)) && + contain_var_clause((Node*)left)); + bool varexpr_right = (bool)(( IsA(right,Func) || IsA (right,Oper)) && + contain_var_clause((Node*)right)); + + if (is_opclause(clause)) { + if(var_left && var_right) { + + *relid1 = left->varno; + *attno1 = left->varoattno; + *relid2 = right->varno; + *attno2 = right->varoattno; + return; + } else if (var_left && varexpr_right ) { + + *relid1 = left->varno; + *attno1 = left->varoattno; + *relid2 = _SELEC_VALUE_UNKNOWN_; + *attno2 = _SELEC_VALUE_UNKNOWN_; + return; + } else if (varexpr_left && var_right) { + + *relid1 = _SELEC_VALUE_UNKNOWN_; + *attno1 = _SELEC_VALUE_UNKNOWN_; + *relid2 = right->varno; + *attno2 = right->varoattno; + return; + } + } + + *relid1 = _SELEC_VALUE_UNKNOWN_; + *attno1 = _SELEC_VALUE_UNKNOWN_; + *relid2 = _SELEC_VALUE_UNKNOWN_; + *attno2 = _SELEC_VALUE_UNKNOWN_; + return; +} + +void +CommuteClause(Node *clause) +{ + Node *temp; + Oper *commu; + OperatorTupleForm commuTup; + HeapTuple heapTup; + + if (!is_opclause(clause)) + return; + + heapTup = (HeapTuple) + get_operator_tuple(get_commutator(((Oper*)((Expr*)clause)->oper)->opno)); + + if (heapTup == (HeapTuple)NULL) + return; + + commuTup = (OperatorTupleForm)GETSTRUCT(heapTup); + + commu = makeOper(heapTup->t_oid, + InvalidOid, + commuTup->oprresult, + ((Oper*)((Expr*)clause)->oper)->opsize, + NULL); + + /* + * reform the clause -> (operator func/var constant) + */ + ((Expr*)clause)->oper = (Node*)commu; + temp = lfirst(((Expr*)clause)->args); + lfirst(((Expr*)clause)->args) = lsecond(((Expr*)clause)->args); + lsecond(((Expr*)clause)->args) = temp; +} + + diff --git a/src/backend/optimizer/util/indexnode.c b/src/backend/optimizer/util/indexnode.c new file mode 100644 index 00000000000..7fd74889202 --- /dev/null +++ b/src/backend/optimizer/util/indexnode.c @@ -0,0 +1,92 @@ +/*------------------------------------------------------------------------- + * + * indexnode.c-- + * Routines to find all indices on a relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/indexnode.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/plannodes.h" +#include "nodes/parsenodes.h" +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/plancat.h" +#include "optimizer/pathnode.h" /* where the decls go */ + + +static List *find_secondary_index(Query *root, Oid relid); + +/* + * find-relation-indices-- + * Returns a list of index nodes containing appropriate information for + * each (secondary) index defined on a relation. + * + */ +List * +find_relation_indices(Query *root, Rel *rel) +{ + if (rel->indexed) { + return (find_secondary_index(root, lfirsti(rel->relids))); + } else { + return (NIL); + } +} + +/* + * find-secondary-index-- + * Creates a list of index path nodes containing information for each + * secondary index defined on a relation by searching through the index + * catalog. + * + * 'relid' is the OID of the relation for which indices are being located + * + * Returns a list of new index nodes. + * + */ +static List * +find_secondary_index(Query *root, Oid relid) +{ + IdxInfoRetval indexinfo; + List *indexes = NIL; + bool first = TRUE; + + while (index_info(root, first, relid,&indexinfo)) { + Rel *indexnode = makeNode(Rel); + + indexnode->relids = lconsi(indexinfo.relid,NIL); + indexnode->relam = indexinfo.relam; + indexnode->pages = indexinfo.pages; + indexnode->tuples = indexinfo.tuples; + indexnode->indexkeys = indexinfo.indexkeys; + indexnode->ordering = indexinfo.orderOprs; + indexnode->classlist = indexinfo.classlist; + indexnode->indproc= indexinfo.indproc; + indexnode->indpred = (List*)indexinfo.indpred; + + indexnode->indexed= false; /* not indexed itself */ + indexnode->size = 0; + indexnode->width= 0; + indexnode->targetlist= NIL; + indexnode->pathlist= NIL; + indexnode->unorderedpath= NULL; + indexnode->cheapestpath= NULL; + indexnode->pruneable= true; + indexnode->clauseinfo= NIL; + indexnode->joininfo= NIL; + indexnode->innerjoin= NIL; + + indexes = lcons(indexnode, indexes); + first = FALSE; + } + + return indexes; +} + diff --git a/src/backend/optimizer/util/internal.c b/src/backend/optimizer/util/internal.c new file mode 100644 index 00000000000..1db22f2b949 --- /dev/null +++ b/src/backend/optimizer/util/internal.c @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------- + * + * internal.c-- + * Definitions required throughout the query optimizer. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/internal.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +/* + * ---------- SHARED MACROS + * + * Macros common to modules for creating, accessing, and modifying + * query tree and query plan components. + * Shared with the executor. + * + */ + + +#include "optimizer/internal.h" + +#include "nodes/relation.h" +#include "nodes/plannodes.h" +#include "nodes/primnodes.h" +#include "utils/elog.h" +#include "utils/palloc.h" + +#if 0 +/***************************************************************************** + * + *****************************************************************************/ + +/* the following should probably be moved elsewhere -ay */ + +TargetEntry * +MakeTLE(Resdom *resdom, Node *expr) +{ + TargetEntry *rt = makeNode(TargetEntry); + rt->resdom = resdom; + rt->expr = expr; + return rt; +} + +Var * +get_expr(TargetEntry *tle) +{ + Assert(tle!=NULL); + Assert(tle->expr!=NULL); + + return ((Var *)tle->expr); +} + +#endif /* 0 */ + + + diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c new file mode 100644 index 00000000000..85416db8b33 --- /dev/null +++ b/src/backend/optimizer/util/joininfo.c @@ -0,0 +1,107 @@ +/*------------------------------------------------------------------------- + * + * joininfo.c-- + * JoinInfo node manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/joininfo.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/var.h" +#include "optimizer/clauses.h" + + +/* + * joininfo-member-- + * Determines whether a node has already been created for a join + * between a set of join relations and the relation described by + * 'joininfo-list'. + * + * 'join-relids' is a list of relids corresponding to the join relation + * 'joininfo-list' is the list of joininfo nodes against which this is + * checked + * + * Returns the corresponding node in 'joininfo-list' if such a node + * exists. + * + */ +JInfo * +joininfo_member(List *join_relids, List *joininfo_list) +{ + List *i = NIL; + List *other_rels = NIL; + + foreach(i,joininfo_list) { + other_rels = lfirst(i); + if(same(join_relids, ((JInfo*)other_rels)->otherrels)) + return((JInfo*)other_rels); + } + return((JInfo*)NULL); +} + + +/* + * find-joininfo-node-- + * Find the joininfo node within a relation entry corresponding + * to a join between 'this_rel' and the relations in 'join-relids'. A + * new node is created and added to the relation entry's joininfo + * field if the desired one can't be found. + * + * Returns a joininfo node. + * + */ +JInfo * +find_joininfo_node(Rel *this_rel, List *join_relids) +{ + JInfo *joininfo = joininfo_member(join_relids, + this_rel->joininfo); + if( joininfo == NULL ) { + joininfo = makeNode(JInfo); + joininfo->otherrels = join_relids; + joininfo->jinfoclauseinfo = NIL; + joininfo->mergesortable = false; + joininfo->hashjoinable = false; + joininfo->inactive = false; + this_rel->joininfo = lcons(joininfo, this_rel->joininfo); + } + return(joininfo); +} + +/* + * other-join-clause-var-- + * Determines whether a var node is contained within a joinclause + * of the form(op var var). + * + * Returns the other var node in the joinclause if it is, nil if not. + * + */ +Var * +other_join_clause_var(Var *var, Expr *clause) +{ + Var *retval; + Var *l, *r; + + retval = (Var*) NULL; + + if( var != NULL && join_clause_p((Node*)clause)) { + l = (Var *) get_leftop(clause); + r = (Var *) get_rightop(clause); + + if(var_equal(var, l)) { + retval = r; + } else if(var_equal(var, r)) { + retval = l; + } + } + + return(retval); +} diff --git a/src/backend/optimizer/util/keys.c b/src/backend/optimizer/util/keys.c new file mode 100644 index 00000000000..ac0915b9096 --- /dev/null +++ b/src/backend/optimizer/util/keys.c @@ -0,0 +1,193 @@ +/*------------------------------------------------------------------------- + * + * keys.c-- + * Key manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/keys.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "nodes/pg_list.h" +#include "nodes/nodes.h" +#include "nodes/relation.h" +#include "utils/elog.h" + +#include "optimizer/internal.h" +#include "optimizer/keys.h" +#include "optimizer/tlist.h" + + +static Expr *matching2_tlvar(int var, List *tlist, bool (*test)()); + +/* + * 1. index key + * one of: + * attnum + * (attnum arrayindex) + * 2. path key + * (subkey1 ... subkeyN) + * where subkeyI is a var node + * note that the 'Keys field is a list of these + * 3. join key + * (outer-subkey inner-subkey) + * where each subkey is a var node + * 4. sort key + * one of: + * SortKey node + * number + * nil + * (may also refer to the 'SortKey field of a SortKey node, + * which looks exactly like an index key) + * + */ + +/* + * match-indexkey-operand-- + * Returns t iff an index key 'index-key' matches the given clause + * operand. + * + */ +bool +match_indexkey_operand(int indexkey, Var *operand, Rel *rel) +{ + if (IsA (operand,Var) && + (lfirsti(rel->relids) == operand->varno) && + equal_indexkey_var(indexkey,operand)) + return(true); + else + return(false); +} + +/* + * equal_indexkey_var-- + * Returns t iff an index key 'index-key' matches the corresponding + * fields of var node 'var'. + * + */ +bool +equal_indexkey_var(int index_key, Var *var) +{ + if (index_key == var->varattno) + return(true); + else + return(false); +} + +/* + * extract-subkey-- + * Returns the subkey in a join key corresponding to the outer or inner + * lelation. + * + */ +Var * +extract_subkey(JoinKey *jk, int which_subkey) +{ + Var *retval; + + switch (which_subkey) { + case OUTER: + retval = jk->outer; + break; + case INNER: + retval = jk->inner; + break; + default: /* do nothing */ + elog(DEBUG,"extract_subkey with neither INNER or OUTER"); + retval = NULL; + } + return(retval); +} + +/* + * samekeys-- + * Returns t iff two sets of path keys are equivalent. They are + * equivalent if the first subkey (var node) within each sublist of + * list 'keys1' is contained within the corresponding sublist of 'keys2'. + * + * XXX It isn't necessary to check that each sublist exactly contain + * the same elements because if the routine that built these + * sublists together is correct, having one element in common + * implies having all elements in common. + * + */ +bool +samekeys(List *keys1, List *keys2) +{ + bool allmember = true; + List *key1, *key2; + + for(key1=keys1,key2=keys2 ; key1 != NIL && key2 !=NIL ; + key1=lnext(key1), key2=lnext(key2)) + if (!member(lfirst(key1), lfirst(key2))) + allmember = false; + + if ( (length (keys2) >= length (keys1)) && allmember) + return(true); + else + return(false); +} + +/* + * collect-index-pathkeys-- + * Creates a list of subkeys by retrieving var nodes corresponding to + * each index key in 'index-keys' from the relation's target list + * 'tlist'. If the key is not in the target list, the key is irrelevant + * and is thrown away. The returned subkey list is of the form: + * ((var1) (var2) ... (varn)) + * + * 'index-keys' is a list of index keys + * 'tlist' is a relation target list + * + * Returns the list of cons'd subkeys. + * + */ +/* This function is identical to matching_tlvar and tlistentry_member. + * They should be merged. + */ +static Expr * +matching2_tlvar(int var, List *tlist, bool (*test)()) +{ + TargetEntry *tlentry = NULL; + + if (var) { + List *temp; + foreach (temp,tlist) { + if ((*test)(var, get_expr(lfirst(temp)))) { + tlentry = lfirst(temp); + break; + } + } + } + + if (tlentry) + return((Expr*)get_expr(tlentry)); + else + return((Expr*)NULL); +} + + +List * +collect_index_pathkeys(int *index_keys, List *tlist) +{ + List *retval = NIL; + + Assert (index_keys != NULL); + + while(index_keys[0] != 0) { + Expr *mvar; + mvar = matching2_tlvar(index_keys[0], + tlist, + equal_indexkey_var); + if (mvar) + retval = nconc(retval,lcons(lcons(mvar,NIL), + NIL)); + index_keys++; + } + return(retval); +} + diff --git a/src/backend/optimizer/util/ordering.c b/src/backend/optimizer/util/ordering.c new file mode 100644 index 00000000000..3dffbff9f3c --- /dev/null +++ b/src/backend/optimizer/util/ordering.c @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------- + * + * ordering.c-- + * Routines to manipulate and compare merge and path orderings + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/Attic/ordering.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "optimizer/internal.h" +#include "optimizer/ordering.h" + + +/* + * equal-path-path-ordering-- + * Returns t iff two path orderings are equal. + * + */ +bool +equal_path_path_ordering(PathOrder *path_ordering1, + PathOrder *path_ordering2) +{ + if (path_ordering1 == path_ordering2) + return true; + + if (!path_ordering1 || !path_ordering2) + return false; + + if (path_ordering1->ordtype == MERGE_ORDER && + path_ordering2->ordtype == MERGE_ORDER) { + + return equal(path_ordering1->ord.merge, path_ordering2->ord.merge); + + } else if (path_ordering1->ordtype == SORTOP_ORDER && + path_ordering2->ordtype == SORTOP_ORDER) { + + return + (equal_sortops_order(path_ordering1->ord.sortop, + path_ordering2->ord.sortop)); + } else if (path_ordering1->ordtype == MERGE_ORDER && + path_ordering2->ordtype == SORTOP_ORDER) { + + return (path_ordering2->ord.sortop && + (path_ordering1->ord.merge->left_operator == + path_ordering2->ord.sortop[0])); + } else { + + return (path_ordering1->ord.sortop && + (path_ordering1->ord.sortop[0] == + path_ordering2->ord.merge->left_operator)); + } +} + +/* + * equal-path-merge-ordering-- + * Returns t iff a path ordering is usable for ordering a merge join. + * + * XXX Presently, this means that the first sortop of the path matches + * either of the merge sortops. Is there a "right" and "wrong" + * sortop to match? + * + */ +bool +equal_path_merge_ordering(Oid *path_ordering, + MergeOrder *merge_ordering) +{ + if (path_ordering == NULL || merge_ordering == NULL) + return(false); + + if (path_ordering[0] == merge_ordering->left_operator || + path_ordering[0] == merge_ordering->right_operator) + return(true); + else + return(false); +} + +/* + * equal-merge-merge-ordering-- + * Returns t iff two merge orderings are equal. + * + */ +bool +equal_merge_merge_ordering(MergeOrder *merge_ordering1, + MergeOrder *merge_ordering2) +{ + return (equal(merge_ordering1, merge_ordering2)); +} + +/***************************************************************************** + * + *****************************************************************************/ + +/* + * equal_sort_ops_order - + * Returns true iff the sort operators are in the same order. + */ +bool +equal_sortops_order(Oid *ordering1, Oid *ordering2) +{ + int i = 0; + + if (ordering1 == NULL || ordering2 == NULL) + return (ordering1==ordering2); + + while (ordering1[i]!=0 && ordering2[i]!=0) { + if (ordering1[i] != ordering2[i]) + break; + i++; + } + + return (ordering1[i]==0 && ordering2[i]==0); +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c new file mode 100644 index 00000000000..728ac9b422e --- /dev/null +++ b/src/backend/optimizer/util/pathnode.c @@ -0,0 +1,566 @@ +/*------------------------------------------------------------------------- + * + * pathnode.c-- + * Routines to manipulate pathlists and create path nodes + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> + +#include "postgres.h" + +#include "nodes/relation.h" +#include "utils/elog.h" + +#include "optimizer/internal.h" +#include "optimizer/pathnode.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/plancat.h" +#include "optimizer/cost.h" +#include "optimizer/keys.h" +#include "optimizer/xfunc.h" +#include "optimizer/ordering.h" + +#include "parser/parsetree.h" /* for getrelid() */ + +static Path *better_path(Path *new_path, List *unique_paths, bool *noOther); + + +/***************************************************************************** + * MISC. PATH UTILITIES + *****************************************************************************/ + +/* + * path-is-cheaper-- + * Returns t iff 'path1' is cheaper than 'path2'. + * + */ +bool +path_is_cheaper(Path *path1, Path *path2) +{ + Cost cost1 = path1->path_cost; + Cost cost2 = path2->path_cost; + + return((bool)(cost1 < cost2)); +} + +/* + * set_cheapest-- + * Finds the minimum cost path from among a relation's paths. + * + * 'parent-rel' is the parent relation + * 'pathlist' is a list of path nodes corresponding to 'parent-rel' + * + * Returns and sets the relation entry field with the pathnode that + * is minimum. + * + */ +Path * +set_cheapest(Rel *parent_rel, List *pathlist) +{ + List *p; + Path *cheapest_so_far; + + Assert(pathlist!=NIL); + Assert(IsA(parent_rel,Rel)); + + cheapest_so_far = (Path*)lfirst(pathlist); + + foreach (p, lnext(pathlist)) { + Path *path = (Path*)lfirst(p); + + if (path_is_cheaper(path, cheapest_so_far)) { + cheapest_so_far = path; + } + } + + parent_rel->cheapestpath = cheapest_so_far; + + return(cheapest_so_far); +} + +/* + * add_pathlist-- + * For each path in the list 'new-paths', add to the list 'unique-paths' + * only those paths that are unique (i.e., unique ordering and ordering + * keys). Should a conflict arise, the more expensive path is thrown out, + * thereby pruning the plan space. But we don't prune if xfunc + * told us not to. + * + * 'parent-rel' is the relation entry to which these paths correspond. + * + * Returns the list of unique pathnodes. + * + */ +List * +add_pathlist(Rel *parent_rel, List *unique_paths, List *new_paths) +{ + List *x; + Path *new_path; + Path *old_path; + bool noOther; + + foreach (x, new_paths) { + new_path = (Path*)lfirst(x); + if (member(new_path, unique_paths)) + continue; + old_path = better_path(new_path,unique_paths,&noOther); + + if (noOther) { + /* Is a brand new path. */ + new_path->parent = parent_rel; + unique_paths = lcons(new_path, unique_paths); + } else if (old_path==NULL) { + ; /* do nothing if path is not cheaper */ + } else if (old_path != NULL) { /* (IsA(old_path,Path)) { */ + new_path->parent = parent_rel; + if (!parent_rel->pruneable) { + unique_paths = lcons(new_path, unique_paths); + }else + unique_paths = lcons(new_path, + LispRemove(old_path,unique_paths)); + } + } + return(unique_paths); +} + +/* + * better_path-- + * Determines whether 'new-path' has the same ordering and keys as some + * path in the list 'unique-paths'. If there is a redundant path, + * eliminate the more expensive path. + * + * Returns: + * The old path - if 'new-path' matches some path in 'unique-paths' and is + * cheaper + * nil - if 'new-path' matches but isn't cheaper + * t - if there is no path in the list with the same ordering and keys + * + */ +static Path * +better_path(Path *new_path, List *unique_paths, bool *noOther) +{ + Path *old_path = (Path*)NULL; + Path *path = (Path*)NULL; + List *temp = NIL; + Path *retval = NULL; + + /* XXX - added the following two lines which weren't int + * the lisp planner, but otherwise, doesn't seem to work + * for the case where new_path is 'nil + */ + foreach (temp,unique_paths) { + path = (Path*) lfirst(temp); + + if ((equal_path_path_ordering(&new_path->p_ordering, + &path->p_ordering) && + samekeys(new_path->keys, path->keys))) { + old_path = path; + break; + } + } + + if (old_path==NULL) { + *noOther = true; + } else { + *noOther = false; + if (path_is_cheaper(new_path,old_path)) { + retval = old_path; + } + } + + return(retval); +} + + + +/***************************************************************************** + * PATH NODE CREATION ROUTINES + *****************************************************************************/ + +/* + * create_seqscan_path-- + * Creates a path corresponding to a sequential scan, returning the + * pathnode. + * + */ +Path * +create_seqscan_path(Rel *rel) +{ + int relid=0; + + Path *pathnode = makeNode(Path); + + pathnode->pathtype = T_SeqScan; + pathnode->parent = rel; + pathnode->path_cost = 0.0; + pathnode->p_ordering.ordtype = SORTOP_ORDER; + pathnode->p_ordering.ord.sortop = NULL; + pathnode->keys = NIL; + /* copy clauseinfo list into path for expensive function processing + * -- JMH, 7/7/92 + */ + pathnode->locclauseinfo= + (List*)copyObject((Node*)rel->clauseinfo); + + if (rel->relids !=NULL) + relid = lfirsti(rel->relids); + + pathnode->path_cost = cost_seqscan (relid, + rel->pages, rel->tuples); + /* add in expensive functions cost! -- JMH, 7/7/92 */ +#if 0 + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost += + xfunc_get_path_cost(pathnode)); + } +#endif + return (pathnode); +} + +/* + * create_index_path-- + * Creates a single path node for an index scan. + * + * 'rel' is the parent rel + * 'index' is the pathnode for the index on 'rel' + * 'restriction-clauses' is a list of restriction clause nodes. + * 'is-join-scan' is a flag indicating whether or not the index is being + * considered because of its sort order. + * + * Returns the new path node. + * + */ +IndexPath * +create_index_path(Query *root, + Rel *rel, + Rel *index, + List *restriction_clauses, + bool is_join_scan) +{ + IndexPath *pathnode = makeNode(IndexPath); + + pathnode->path.pathtype = T_IndexScan; + pathnode->path.parent = rel; + pathnode->indexid = index->relids; + + pathnode->path.p_ordering.ordtype = SORTOP_ORDER; + pathnode->path.p_ordering.ord.sortop = index->ordering; + pathnode->indexqual = NIL; + + /* copy clauseinfo list into path for expensive function processing + * -- JMH, 7/7/92 + */ + pathnode->path.locclauseinfo = + set_difference((List*) copyObject((Node*)rel->clauseinfo), + (List*) restriction_clauses); + + /* + * The index must have an ordering for the path to have (ordering) keys, + * and vice versa. + */ + if (pathnode->path.p_ordering.ord.sortop) { + pathnode->path.keys = collect_index_pathkeys(index->indexkeys, + rel->targetlist); + /* + * Check that the keys haven't 'disappeared', since they may + * no longer be in the target list (i.e., index keys that are not + * relevant to the scan are not applied to the scan path node, + * so if no index keys were found, we can't order the path). + */ + if (pathnode->path.keys==NULL) { + pathnode->path.p_ordering.ord.sortop = NULL; + } + } else { + pathnode->path.keys = NULL; + } + + if (is_join_scan || restriction_clauses==NULL) { + /* + * Indices used for joins or sorting result nodes don't + * restrict the result at all, they simply order it, + * so compute the scan cost + * accordingly -- use a selectivity of 1.0. + */ +/* is the statement above really true? what about IndexScan as the + inner of a join? */ + pathnode->path.path_cost = + cost_index (lfirsti(index->relids), + index->pages, + 1.0, + rel->pages, + rel->tuples, + index->pages, + index->tuples, + false); + /* add in expensive functions cost! -- JMH, 7/7/92 */ +#if 0 + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost = + (pathnode->path_cost + + xfunc_get_path_cost((Path*)pathnode)); + } +#endif + } else { + /* + * Compute scan cost for the case when 'index' is used with a + * restriction clause. + */ + List *attnos; + List *values; + List *flags; + float npages; + float selec; + Cost clausesel; + + get_relattvals(restriction_clauses, + &attnos, + &values, + &flags); + index_selectivity(lfirsti(index->relids), + index->classlist, + get_opnos(restriction_clauses), + getrelid(lfirsti(rel->relids), + root->rtable), + attnos, + values, + flags, + length(restriction_clauses), + &npages, + &selec); + /* each clause gets an equal selectivity */ + clausesel = + pow(selec, + 1.0 / (double) length(restriction_clauses)); + + pathnode->indexqual = restriction_clauses; + pathnode->path.path_cost = + cost_index (lfirsti(index->relids), + (int)npages, + selec, + rel->pages, + rel->tuples, + index->pages, + index->tuples, + false); + +#if 0 + /* add in expensive functions cost! -- JMH, 7/7/92 */ + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost += + xfunc_get_path_cost((Path*)pathnode); + } +#endif + /* Set selectivities of clauses used with index to the selectivity + * of this index, subdividing the selectivity equally over each of + * the clauses. + */ + + /* XXX Can this divide the selectivities in a better way? */ + set_clause_selectivities(restriction_clauses, clausesel); + } + return(pathnode); +} + +/* + * create_nestloop_path-- + * Creates a pathnode corresponding to a nestloop join between two + * relations. + * + * 'joinrel' is the join relation. + * 'outer_rel' is the outer join relation + * 'outer_path' is the outer join path. + * 'inner_path' is the inner join path. + * 'keys' are the keys of the path + * + * Returns the resulting path node. + * + */ +JoinPath * +create_nestloop_path(Rel *joinrel, + Rel *outer_rel, + Path *outer_path, + Path *inner_path, + List *keys) +{ + JoinPath *pathnode = makeNode(JoinPath); + + pathnode->path.pathtype = T_NestLoop; + pathnode->path.parent = joinrel; + pathnode->outerjoinpath = outer_path; + pathnode->innerjoinpath = inner_path; + pathnode->pathclauseinfo = joinrel->clauseinfo; + pathnode->path.keys = keys; + pathnode->path.joinid = NIL; + pathnode->path.outerjoincost = (Cost)0.0; + pathnode->path.locclauseinfo = NIL; + + if (keys) { + pathnode->path.p_ordering.ordtype = + outer_path->p_ordering.ordtype; + if (outer_path->p_ordering.ordtype == SORTOP_ORDER) { + pathnode->path.p_ordering.ord.sortop = + outer_path->p_ordering.ord.sortop; + } else { + pathnode->path.p_ordering.ord.merge = + outer_path->p_ordering.ord.merge; + } + } else { + pathnode->path.p_ordering.ordtype = SORTOP_ORDER; + pathnode->path.p_ordering.ord.sortop = NULL; + } + + pathnode->path.path_cost = + cost_nestloop(outer_path->path_cost, + inner_path->path_cost, + outer_rel->size, + inner_path->parent->size, + page_size(outer_rel->size, + outer_rel->width), + IsA(inner_path,IndexPath)); + /* add in expensive function costs -- JMH 7/7/92 */ +#if 0 + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost += xfunc_get_path_cost((Path*)pathnode); + } +#endif + return(pathnode); +} + +/* + * create_mergesort_path-- + * Creates a pathnode corresponding to a mergesort join between + * two relations + * + * 'joinrel' is the join relation + * 'outersize' is the number of tuples in the outer relation + * 'innersize' is the number of tuples in the inner relation + * 'outerwidth' is the number of bytes per tuple in the outer relation + * 'innerwidth' is the number of bytes per tuple in the inner relation + * 'outer_path' is the outer path + * 'inner_path' is the inner path + * 'keys' are the new keys of the join relation + * 'order' is the sort order required for the merge + * 'mergeclauses' are the applicable join/restriction clauses + * 'outersortkeys' are the sort varkeys for the outer relation + * 'innersortkeys' are the sort varkeys for the inner relation + * + */ +MergePath * +create_mergesort_path(Rel *joinrel, + int outersize, + int innersize, + int outerwidth, + int innerwidth, + Path *outer_path, + Path *inner_path, + List *keys, + MergeOrder *order, + List *mergeclauses, + List *outersortkeys, + List *innersortkeys) +{ + MergePath *pathnode = makeNode(MergePath); + + pathnode->jpath.path.pathtype = T_MergeJoin; + pathnode->jpath.path.parent = joinrel; + pathnode->jpath.outerjoinpath = outer_path; + pathnode->jpath.innerjoinpath = inner_path; + pathnode->jpath.pathclauseinfo = joinrel->clauseinfo; + pathnode->jpath.path.keys = keys; + pathnode->jpath.path.p_ordering.ordtype = MERGE_ORDER; + pathnode->jpath.path.p_ordering.ord.merge = order; + pathnode->path_mergeclauses = mergeclauses; + pathnode->jpath.path.locclauseinfo = NIL; + pathnode->outersortkeys = outersortkeys; + pathnode->innersortkeys = innersortkeys; + pathnode->jpath.path.path_cost = + cost_mergesort(outer_path->path_cost, + inner_path->path_cost, + outersortkeys, + innersortkeys, + outersize, + innersize, + outerwidth, + innerwidth); + /* add in expensive function costs -- JMH 7/7/92 */ +#if 0 + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost += + xfunc_get_path_cost((Path*)pathnode); + } +#endif + return(pathnode); +} + +/* + * create_hashjoin_path-- XXX HASH + * Creates a pathnode corresponding to a hash join between two relations. + * + * 'joinrel' is the join relation + * 'outersize' is the number of tuples in the outer relation + * 'innersize' is the number of tuples in the inner relation + * 'outerwidth' is the number of bytes per tuple in the outer relation + * 'innerwidth' is the number of bytes per tuple in the inner relation + * 'outer_path' is the outer path + * 'inner_path' is the inner path + * 'keys' are the new keys of the join relation + * 'operator' is the hashjoin operator + * 'hashclauses' are the applicable join/restriction clauses + * 'outerkeys' are the sort varkeys for the outer relation + * 'innerkeys' are the sort varkeys for the inner relation + * + */ +HashPath * +create_hashjoin_path(Rel *joinrel, + int outersize, + int innersize, + int outerwidth, + int innerwidth, + Path *outer_path, + Path *inner_path, + List *keys, + Oid operator, + List *hashclauses, + List *outerkeys, + List *innerkeys) +{ + HashPath *pathnode = makeNode(HashPath); + + pathnode->jpath.path.pathtype = T_HashJoin; + pathnode->jpath.path.parent = joinrel; + pathnode->jpath.outerjoinpath = outer_path; + pathnode->jpath.innerjoinpath = inner_path; + pathnode->jpath.pathclauseinfo = joinrel->clauseinfo; + pathnode->jpath.path.locclauseinfo = NIL; + pathnode->jpath.path.keys = keys; + pathnode->jpath.path.p_ordering.ordtype = SORTOP_ORDER; + pathnode->jpath.path.p_ordering.ord.sortop = NULL; + pathnode->jpath.path.outerjoincost = (Cost)0.0; + pathnode->jpath.path.joinid = (Relid)NULL; + /* pathnode->hashjoinoperator = operator; */ + pathnode->path_hashclauses = hashclauses; + pathnode->outerhashkeys = outerkeys; + pathnode->innerhashkeys = innerkeys; + pathnode->jpath.path.path_cost = + cost_hashjoin(outer_path->path_cost, + inner_path->path_cost, + outerkeys, + innerkeys, + outersize,innersize, + outerwidth,innerwidth); + /* add in expensive function costs -- JMH 7/7/92 */ +#if 0 + if (XfuncMode != XFUNC_OFF) { + pathnode->path_cost += + xfunc_get_path_cost((Path*)pathnode); + } +#endif + return(pathnode); +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c new file mode 100644 index 00000000000..4dca017f95a --- /dev/null +++ b/src/backend/optimizer/util/plancat.c @@ -0,0 +1,582 @@ +/*------------------------------------------------------------------------- + * + * plancat.c-- + * routines for accessing the system catalogs + * + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.1.1.1 1996/07/09 06:21:39 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include "postgres.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup.h" +#include "access/itup.h" + +#include "catalog/catname.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_index.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_version.h" + +#include "nodes/pg_list.h" +#include "parser/parsetree.h" /* for getrelid() */ +#include "fmgr.h" + +#include "optimizer/internal.h" +#include "optimizer/plancat.h" + +#include "utils/tqual.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/syscache.h" + + +static void IndexSelectivity(Oid indexrelid, Oid indrelid, int32 nIndexKeys, + Oid AccessMethodOperatorClasses[], Oid operatorObjectIds[], + int32 varAttributeNumbers[], char *constValues[], int32 constFlags[], + float *idxPages, float *idxSelec); + + +/* + * relation-info - + * Retrieves catalog information for a given relation. Given the oid of + * the relation, return the following information: + * whether the relation has secondary indices + * number of pages + * number of tuples + */ +void +relation_info(Query *root, Index relid, + bool *hasindex, int *pages, int *tuples) +{ + HeapTuple relationTuple; + Form_pg_class relation; + Oid relationObjectId; + + relationObjectId = getrelid(relid, root->rtable); + relationTuple = SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(relationObjectId), + 0,0,0); + if (HeapTupleIsValid(relationTuple)) { + relation = (Form_pg_class)GETSTRUCT(relationTuple); + + *hasindex = (relation->relhasindex) ? TRUE : FALSE; + *pages = relation->relpages; + *tuples = relation->reltuples; + } else { + elog(WARN, "RelationCatalogInformation: Relation %d not found", + relationObjectId); + } + + return; +} + + +/* + * index-info-- + * Retrieves catalog information on an index on a given relation. + * + * The index relation is opened on the first invocation. The current + * retrieves the next index relation within the catalog that has not + * already been retrieved by a previous call. The index catalog + * is closed when no more indices for 'relid' can be found. + * + * 'first' is 1 if this is the first call + * + * Returns true if successful and false otherwise. Index info is returned + * via the transient data structure 'info'. + * + */ +bool +index_info(Query *root, bool first, int relid, IdxInfoRetval *info) +{ + register i; + HeapTuple indexTuple, amopTuple; + IndexTupleForm index; + Relation indexRelation; + uint16 amstrategy; + Oid relam; + Oid indrelid; + + static Relation relation = (Relation) NULL; + static HeapScanDesc scan = (HeapScanDesc) NULL; + static ScanKeyData indexKey; + + + /* find the oid of the indexed relation */ + indrelid = getrelid(relid, root->rtable); + + memset(info, 0, sizeof(IdxInfoRetval)); + + /* + * the maximum number of elements in each of the following arrays is + * 8. We allocate one more for a terminating 0 to indicate the end + * of the array. + */ + info->indexkeys = (int *)palloc(sizeof(int)*9); + memset(info->indexkeys, 0, sizeof(int)*9); + info->orderOprs = (Oid *)palloc(sizeof(Oid)*9); + memset(info->orderOprs, 0, sizeof(Oid)*9); + info->classlist = (Oid *)palloc(sizeof(Oid)*9); + memset(info->classlist, 0, sizeof(Oid)*9); + + /* Find an index on the given relation */ + if (first) { + if (RelationIsValid(relation)) + heap_close(relation); + if (HeapScanIsValid(scan)) + heap_endscan(scan); + + ScanKeyEntryInitialize(&indexKey, 0, + Anum_pg_index_indrelid, + F_OIDEQ, + ObjectIdGetDatum(indrelid)); + + relation = heap_openr(IndexRelationName); + scan = heap_beginscan(relation, 0, NowTimeQual, + 1, &indexKey); + } + if (!HeapScanIsValid(scan)) + elog(WARN, "index_info: scan not started"); + indexTuple = heap_getnext(scan, 0, (Buffer *) NULL); + if (!HeapTupleIsValid(indexTuple)) { + heap_endscan(scan); + heap_close(relation); + scan = (HeapScanDesc) NULL; + relation = (Relation) NULL; + return(0); + } + + /* Extract info from the index tuple */ + index = (IndexTupleForm)GETSTRUCT(indexTuple); + info->relid = index->indexrelid; /* index relation */ + for (i = 0; i < 8; i++) + info->indexkeys[i] = index->indkey[i]; + for (i = 0; i < 8; i++) + info->classlist[i] = index->indclass[i]; + + info->indproc = index->indproc; /* functional index ?? */ + + /* partial index ?? */ + if (VARSIZE(&index->indpred) != 0) { + /* + * The memory allocated here for the predicate (in lispReadString) + * only needs to stay around until it's used in find_index_paths, + * which is all within a command, so the automatic pfree at end + * of transaction should be ok. + */ + char *predString; + + predString = fmgr(F_TEXTOUT, &index->indpred); + info->indpred = (Node*)stringToNode(predString); + pfree(predString); + } + + /* Extract info from the relation descriptor for the index */ + indexRelation = index_open(index->indexrelid); +#ifdef notdef + /* XXX should iterate through strategies -- but how? use #1 for now */ + amstrategy = indexRelation->rd_am->amstrategies; +#endif /* notdef */ + amstrategy = 1; + relam = indexRelation->rd_rel->relam; + info->relam = relam; + info->pages = indexRelation->rd_rel->relpages; + info->tuples = indexRelation->rd_rel->reltuples; + heap_close(indexRelation); + + /* + * Find the index ordering keys + * + * Must use indclass to know when to stop looking since with + * functional indices there could be several keys (args) for + * one opclass. -mer 27 Sept 1991 + */ + for (i = 0; i < 8 && index->indclass[i]; ++i) { + amopTuple = SearchSysCacheTuple(AMOPSTRATEGY, + ObjectIdGetDatum(relam), + ObjectIdGetDatum(index->indclass[i]), + UInt16GetDatum(amstrategy), + 0); + if (!HeapTupleIsValid(amopTuple)) + elog(WARN, "index_info: no amop %d %d %d", + relam, index->indclass[i], amstrategy); + info->orderOprs[i] = + ((Form_pg_amop)GETSTRUCT(amopTuple))->amopopr; + } + return(TRUE); +} + +/* + * index-selectivity-- + * + * Call util/plancat.c:IndexSelectivity with the indicated arguments. + * + * 'indid' is the index OID + * 'classes' is a list of index key classes + * 'opnos' is a list of index key operator OIDs + * 'relid' is the OID of the relation indexed + * 'attnos' is a list of the relation attnos which the index keys over + * 'values' is a list of the values of the clause's constants + * 'flags' is a list of fixnums which describe the constants + * 'nkeys' is the number of index keys + * + * Returns two floats: index pages and index selectivity in 'idxPages' and + * 'idxSelec'. + * + */ +void +index_selectivity(Oid indid, + Oid *classes, + List *opnos, + Oid relid, + List *attnos, + List *values, + List *flags, + int32 nkeys, + float *idxPages, + float *idxSelec) +{ + Oid *opno_array; + int *attno_array, *flag_array; + char **value_array; + int i = 0; + List *xopno, *xattno, *value, *flag; + + if (length(opnos)!=nkeys || length(attnos)!=nkeys || + length(values)!=nkeys || length(flags)!=nkeys) { + + *idxPages = 0.0; + *idxSelec = 1.0; + return; + } + + opno_array = (Oid *)palloc(nkeys*sizeof(Oid)); + attno_array = (int *)palloc(nkeys*sizeof(int32)); + value_array = (char **)palloc(nkeys*sizeof(char *)); + flag_array = (int *)palloc(nkeys*sizeof(int32)); + + i = 0; + foreach(xopno, opnos) { + opno_array[i++] = (int)lfirst(xopno); + } + + i = 0; + foreach(xattno,attnos) { + attno_array[i++] = (int)lfirst(xattno); + } + + i = 0; + foreach(value, values) { + value_array[i++] = (char *)lfirst(value); + } + + i = 0; + foreach(flag,flags) { + flag_array[i++] = (int)lfirst(flag); + } + + IndexSelectivity(indid, + relid, + nkeys, + classes, /* not used */ + opno_array, + attno_array, + value_array, + flag_array, + idxPages, + idxSelec); + return; +} + +/* + * restriction_selectivity in lisp system.-- + * + * NOTE: The routine is now merged with RestrictionClauseSelectivity + * as defined in plancat.c + * + * Returns the selectivity of a specified operator. + * This code executes registered procedures stored in the + * operator relation, by calling the function manager. + * + * XXX The assumption in the selectivity procedures is that if the + * relation OIDs or attribute numbers are -1, then the clause + * isn't of the form (op var const). + */ +Cost +restriction_selectivity(Oid functionObjectId, + Oid operatorObjectId, + Oid relationObjectId, + AttrNumber attributeNumber, + char *constValue, + int32 constFlag) +{ + float64 result; + + result = (float64) fmgr(functionObjectId, + (char *) operatorObjectId, + (char *) relationObjectId, + (char *) attributeNumber, + (char *) constValue, + (char *) constFlag, + NULL); + if (!PointerIsValid(result)) + elog(WARN, "RestrictionClauseSelectivity: bad pointer"); + + if (*result < 0.0 || *result > 1.0) + elog(WARN, "RestrictionClauseSelectivity: bad value %lf", + *result); + + return ((Cost)*result); +} + +/* + * join_selectivity-- + * Similarly, this routine is merged with JoinClauseSelectivity in + * plancat.c + * + * Returns the selectivity of an operator, given the join clause + * information. + * + * XXX The assumption in the selectivity procedures is that if the + * relation OIDs or attribute numbers are -1, then the clause + * isn't of the form (op var var). + */ +Cost +join_selectivity (Oid functionObjectId, + Oid operatorObjectId, + Oid relationObjectId1, + AttrNumber attributeNumber1, + Oid relationObjectId2, + AttrNumber attributeNumber2) +{ + float64 result; + + result = (float64) fmgr(functionObjectId, + (char *) operatorObjectId, + (char *) relationObjectId1, + (char *) attributeNumber1, + (char *) relationObjectId2, + (char *) attributeNumber2, + NULL); + if (!PointerIsValid(result)) + elog(WARN, "JoinClauseSelectivity: bad pointer"); + + if (*result < 0.0 || *result > 1.0) + elog(WARN, "JoinClauseSelectivity: bad value %lf", + *result); + + return((Cost)*result); +} + +/* + * find_all_inheritors-- + * + * Returns a LISP list containing the OIDs of all relations which + * inherits from the relation with OID 'inhparent'. + */ +List * +find_inheritance_children(Oid inhparent) +{ + static ScanKeyData key[1] = { + { 0, Anum_pg_inherits_inhparent, F_OIDEQ } + }; + + HeapTuple inheritsTuple; + Relation relation; + HeapScanDesc scan; + List *list = NIL; + Oid inhrelid; + + fmgr_info(F_OIDEQ, &key[0].sk_func, &key[0].sk_nargs); + + key[0].sk_argument = ObjectIdGetDatum((Oid)inhparent); + relation = heap_openr(InheritsRelationName); + scan = heap_beginscan(relation, 0, NowTimeQual, 1, key); + while (HeapTupleIsValid(inheritsTuple = + heap_getnext(scan, 0, + (Buffer *) NULL))) { + inhrelid = ((InheritsTupleForm)GETSTRUCT(inheritsTuple))->inhrel; + list = lappendi(list, inhrelid); + } + heap_endscan(scan); + heap_close(relation); + return(list); +} + +/* + * VersionGetParents-- + * + * Returns a LISP list containing the OIDs of all relations which are + * base relations of the relation with OID 'verrelid'. + */ +List * +VersionGetParents(Oid verrelid) +{ + static ScanKeyData key[1] = { + { 0, Anum_pg_version_verrelid, F_OIDEQ } + }; + + HeapTuple versionTuple; + Relation relation; + HeapScanDesc scan; + Oid verbaseid; + List *list= NIL; + + fmgr_info(F_OIDEQ, &key[0].sk_func, &key[0].sk_nargs); + relation = heap_openr(VersionRelationName); + key[0].sk_argument = ObjectIdGetDatum(verrelid); + scan = heap_beginscan(relation, 0, NowTimeQual, 1, key); + for (;;) { + versionTuple = heap_getnext(scan, 0, + (Buffer *) NULL); + if (!HeapTupleIsValid(versionTuple)) + break; + verbaseid = ((VersionTupleForm) + GETSTRUCT(versionTuple))->verbaseid; + + list = lconsi(verbaseid, list); + + key[0].sk_argument = ObjectIdGetDatum(verbaseid); + heap_rescan(scan, 0, key); + } + heap_endscan(scan); + heap_close(relation); + return(list); +} + +/***************************************************************************** + * + *****************************************************************************/ + +/* + * IdexSelectivity-- + * + * Retrieves the 'amopnpages' and 'amopselect' parameters for each + * AM operator when a given index (specified by 'indexrelid') is used. + * These two parameters are returned by copying them to into an array of + * floats. + * + * Assumption: the attribute numbers and operator ObjectIds are in order + * WRT to each other (otherwise, you have no way of knowing which + * AM operator class or attribute number corresponds to which operator. + * + * 'varAttributeNumbers' contains attribute numbers for variables + * 'constValues' contains the constant values + * 'constFlags' describes how to treat the constants in each clause + * 'nIndexKeys' describes how many keys the index actually has + * + * Returns 'selectivityInfo' filled with the sum of all pages touched + * and the product of each clause's selectivity. + * + */ +static void +IndexSelectivity(Oid indexrelid, + Oid indrelid, + int32 nIndexKeys, + Oid AccessMethodOperatorClasses[], /* XXX not used? */ + Oid operatorObjectIds[], + int32 varAttributeNumbers[], + char *constValues[], + int32 constFlags[], + float *idxPages, + float *idxSelec) +{ + register i, n; + HeapTuple indexTuple, amopTuple, indRel; + IndexTupleForm index; + Form_pg_amop amop; + Oid indclass; + float64data npages, select; + float64 amopnpages, amopselect; + Oid relam; + + indRel = SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(indexrelid), + 0,0,0); + if (!HeapTupleIsValid(indRel)) + elog(WARN, "IndexSelectivity: index %d not found", + indexrelid); + relam = ((Form_pg_class)GETSTRUCT(indRel))->relam; + + indexTuple = SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(indexrelid), + 0,0,0); + if (!HeapTupleIsValid(indexTuple)) + elog(WARN, "IndexSelectivity: index %d not found", + indexrelid); + index = (IndexTupleForm)GETSTRUCT(indexTuple); + + npages = 0.0; + select = 1.0; + for (n = 0; n < nIndexKeys; ++n) { + /* + * Find the AM class for this key. + * + * If the first attribute number is invalid then we have a + * functional index, and AM class is the first one defined + * since functional indices have exactly one key. + */ + indclass = (varAttributeNumbers[0] == InvalidAttrNumber) ? + index->indclass[0] : InvalidOid; + i = 0; + while ((i < nIndexKeys) && (indclass == InvalidOid)) { + if (varAttributeNumbers[n] == index->indkey[i]) { + indclass = index->indclass[i]; + break; + } + i++; + } + if (!OidIsValid(indclass)) { + /* + * Presumably this means that we are using a functional + * index clause and so had no variable to match to + * the index key ... if not we are in trouble. + */ + elog(NOTICE, "IndexSelectivity: no key %d in index %d", + varAttributeNumbers[n], indexrelid); + continue; + } + + amopTuple = SearchSysCacheTuple(AMOPOPID, + ObjectIdGetDatum(indclass), + ObjectIdGetDatum(operatorObjectIds[n]), + ObjectIdGetDatum(relam), + 0); + if (!HeapTupleIsValid(amopTuple)) + elog(WARN, "IndexSelectivity: no amop %d %d", + indclass, operatorObjectIds[n]); + amop = (Form_pg_amop)GETSTRUCT(amopTuple); + amopnpages = (float64) fmgr(amop->amopnpages, + (char *) operatorObjectIds[n], + (char *) indrelid, + (char *) varAttributeNumbers[n], + (char *) constValues[n], + (char *) constFlags[n], + (char *) nIndexKeys, + (char *) indexrelid); + npages += PointerIsValid(amopnpages) ? *amopnpages : 0.0; + if ((i = npages) < npages) /* ceil(npages)? */ + npages += 1.0; + amopselect = (float64) fmgr(amop->amopselect, + (char *) operatorObjectIds[n], + (char *) indrelid, + (char *) varAttributeNumbers[n], + (char *) constValues[n], + (char *) constFlags[n], + (char *) nIndexKeys, + (char *) indexrelid); + select *= PointerIsValid(amopselect) ? *amopselect : 1.0; + } + *idxPages = npages; + *idxSelec = select; +} + diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c new file mode 100644 index 00000000000..351fb182107 --- /dev/null +++ b/src/backend/optimizer/util/relnode.c @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------- + * + * relnode.c-- + * Relation manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.1.1.1 1996/07/09 06:21:39 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/relation.h" + +#include "optimizer/internal.h" +#include "optimizer/pathnode.h" /* where the decls go */ +#include "optimizer/plancat.h" + + + +/* + * get_base_rel-- + * Returns relation entry corresponding to 'relid', creating a new one if + * necessary. This is for base relations. + * + */ +Rel *get_base_rel(Query* root, int relid) +{ + List *relids; + Rel *rel; + + relids = lconsi(relid, NIL); + rel = rel_member(relids, root->base_relation_list_); + if (rel==NULL) { + rel = makeNode(Rel); + rel->relids = relids; + rel->indexed = false; + rel->pages = 0; + rel->tuples = 0; + rel->width = 0; + rel->targetlist = NIL; + rel->pathlist = NIL; + rel->unorderedpath = (Path *)NULL; + rel->cheapestpath = (Path *)NULL; + rel->pruneable = true; + rel->classlist = NULL; + rel->ordering = NULL; + rel->relam = InvalidOid; + rel->clauseinfo = NIL; + rel->joininfo = NIL; + rel->innerjoin = NIL; + rel->superrels = NIL; + + root->base_relation_list_ = lcons(rel, + root->base_relation_list_); + + /* + * ??? the old lispy C code (get_rel) do a listp(relid) here but + * that can never happen since we already established relid is not + * a list. -ay 10/94 + */ + if(relid < 0) { + /* + * If the relation is a materialized relation, assume + * constants for sizes. + */ + rel->pages = _TEMP_RELATION_PAGES_; + rel->tuples = _TEMP_RELATION_TUPLES_; + + } else { + bool hasindex; + int pages, tuples; + + /* + * Otherwise, retrieve relation characteristics from the + * system catalogs. + */ + relation_info(root, relid, &hasindex, &pages, &tuples); + rel->indexed = hasindex; + rel->pages = pages; + rel->tuples = tuples; + } + } + return rel; +} + +/* + * get_join_rel-- + * Returns relation entry corresponding to 'relid' (a list of relids), + * creating a new one if necessary. This is for join relations. + * + */ +Rel *get_join_rel(Query *root, List *relid) +{ + return rel_member(relid, root->join_relation_list_); +} + +/* + * rel-member-- + * Determines whether a relation of id 'relid' is contained within a list + * 'rels'. + * + * Returns the corresponding entry in 'rels' if it is there. + * + */ +Rel * +rel_member(List *relid, List *rels) +{ + List *temp = NIL; + List *temprelid = NIL; + + if (relid!=NIL && rels!=NIL) { + foreach(temp,rels) { + temprelid = ((Rel*)lfirst(temp))->relids; + if(same(temprelid, relid)) + return((Rel*)(lfirst(temp))); + } + } + return(NULL); +} diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c new file mode 100644 index 00000000000..073c2a08231 --- /dev/null +++ b/src/backend/optimizer/util/tlist.c @@ -0,0 +1,577 @@ +/*------------------------------------------------------------------------- + * + * tlist.c-- + * Target list manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/tlist.c,v 1.1.1.1 1996/07/09 06:21:39 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/relation.h" +#include "nodes/primnodes.h" +#include "nodes/pg_list.h" +#include "nodes/nodeFuncs.h" +#include "utils/elog.h" +#include "utils/lsyscache.h" + +#include "optimizer/internal.h" +#include "optimizer/var.h" +#include "optimizer/tlist.h" +#include "optimizer/clauses.h" + +#include "nodes/makefuncs.h" +#include "parser/catalog_utils.h" + +static Node *flatten_tlistentry(Node *tlistentry, List *flat_tlist); + +/***************************************************************************** + * ---------- RELATION node target list routines ---------- + *****************************************************************************/ + +/* + * tlistentry-member-- + * + * RETURNS: the leftmost member of sequence "targetlist" that satisfies + * the predicate "var_equal" + * MODIFIES: nothing + * REQUIRES: test = function which can operate on a lispval union + * var = valid var-node + * targetlist = valid sequence + */ +TargetEntry * +tlistentry_member(Var *var, List *targetlist) +{ + if (var) { + List *temp = NIL; + + foreach (temp,targetlist) { + if (var_equal(var, + get_expr(lfirst(temp)))) + return((TargetEntry*)lfirst(temp)); + } + } + return (NULL); +} + +/* + * matching_tlvar-- + * + * RETURNS: var node in a target list which is var_equal to 'var', + * if one exists. + * REQUIRES: "test" operates on lispval unions, + * + */ +Expr * +matching_tlvar(Var *var, List *targetlist) +{ + TargetEntry *tlentry; + + tlentry = tlistentry_member(var,targetlist); + if (tlentry) + return((Expr*)get_expr (tlentry) ); + + return((Expr*) NULL); +} + +/* + * add_tl_element-- + * Creates a targetlist entry corresponding to the supplied var node + * + * 'var' and adds the new targetlist entry to the targetlist field of + * 'rel' + * + * RETURNS: nothing + * MODIFIES: vartype and varid fields of leftmost varnode that matches + * argument "var" (sometimes). + * CREATES: new var-node iff no matching var-node exists in targetlist + */ +void +add_tl_element(Rel *rel, Var *var) +{ + Expr *oldvar = (Expr *)NULL; + + oldvar = matching_tlvar(var, rel->targetlist); + + /* + * If 'var' is not already in 'rel's target list, add a new node. + */ + if (oldvar==NULL) { + List *tlist = rel->targetlist; + Var *newvar = makeVar(var->varno, + var->varattno, + var->vartype, + var->varno, + var->varoattno); + + rel->targetlist = + lappend (tlist, + create_tl_element(newvar, + length(tlist) + 1)); + + } +} + +/* + * create_tl_element-- + * Creates a target list entry node and its associated (resdom var) pair + * with its resdom number equal to 'resdomno' and the joinlist field set + * to 'joinlist'. + * + * RETURNS: newly created tlist-entry + * CREATES: new targetlist entry (always). + */ +TargetEntry* +create_tl_element(Var *var, int resdomno) +{ + TargetEntry *tlelement= makeNode(TargetEntry); + + tlelement->resdom = + makeResdom(resdomno, + var->vartype, + get_typlen(var->vartype), + NULL, + (Index)0, + (Oid)0, + 0); + tlelement->expr = (Node*)var; + + return(tlelement); +} + +/* + * get-actual-tlist-- + * Returns the targetlist elements from a relation tlist. + * + */ +List * +get_actual_tlist(List *tlist) +{ + /* + * this function is not making sense. - ay 10/94 + */ +#if 0 + List *element = NIL; + List *result = NIL; + + if (tlist==NULL) { + elog(DEBUG,"calling get_actual_tlist with empty tlist"); + return(NIL); + } + /* XXX - it is unclear to me what exactly get_entry + should be doing, as it is unclear to me the exact + relationship between "TL" "TLE" and joinlists */ + + foreach(element,tlist) + result = lappend(result, lfirst((List*)lfirst(element))); + + return(result); +#endif + return tlist; +} + +/***************************************************************************** + * ---------- GENERAL target list routines ---------- + *****************************************************************************/ + +/* + * tlist-member-- + * Determines whether a var node is already contained within a + * target list. + * + * 'var' is the var node + * 'tlist' is the target list + * 'dots' is t if we must match dotfields to determine uniqueness + * + * Returns the resdom entry of the matching var node. + * + */ +Resdom * +tlist_member(Var *var, List *tlist) +{ + List *i = NIL; + TargetEntry *temp_tle = (TargetEntry *)NULL; + TargetEntry *tl_elt = (TargetEntry *)NULL; + + if (var) { + foreach (i,tlist) { + temp_tle = (TargetEntry *)lfirst(i); + if (var_equal(var, get_expr(temp_tle))) { + tl_elt = temp_tle; + break; + } + } + + if (tl_elt != NULL) + return(tl_elt->resdom); + else + return((Resdom*)NULL); + } + return ((Resdom*)NULL); +} + +/* + * Routine to get the resdom out of a targetlist. + */ +Resdom * +tlist_resdom(List *tlist, Resdom *resnode) +{ + Resdom *resdom = (Resdom*)NULL; + List *i = NIL; + TargetEntry *temp_tle = (TargetEntry *)NULL; + + foreach(i,tlist) { + temp_tle = (TargetEntry *)lfirst(i); + resdom = temp_tle->resdom; + /* Since resnos are supposed to be unique */ + if (resnode->resno == resdom->resno) + return(resdom); + } + return((Resdom*)NULL); +} + + +/* + * match_varid-- + * Searches a target list for an entry with some desired varid. + * + * 'varid' is the desired id + * 'tlist' is the target list that is searched + * + * Returns the target list entry (resdom var) of the matching var. + * + * Now checks to make sure array references (in addition to range + * table indices) are identical - retrieve (a.b[1],a.b[2]) should + * not be turned into retrieve (a.b[1],a.b[1]). + * + * [what used to be varid is now broken up into two fields varnoold and + * varoattno. Also, nested attnos are long gone. - ay 2/95] + */ +TargetEntry * +match_varid(Var *test_var, List *tlist) +{ + List *tl; + Oid type_var; + + type_var = (Oid) test_var->vartype; + + foreach (tl, tlist) { + TargetEntry *entry; + Var *tlvar; + + entry = lfirst(tl); + tlvar = get_expr(entry); + + /* + * we test the original varno (instead of varno which might + * be changed to INNER/OUTER. + */ + if (tlvar->varnoold == test_var->varnoold && + tlvar->varoattno == test_var->varoattno) { + + if (tlvar->vartype == type_var) + return(entry); + } + } + + return (NULL); +} + + +/* + * new-unsorted-tlist-- + * Creates a copy of a target list by creating new resdom nodes + * without sort information. + * + * 'targetlist' is the target list to be copied. + * + * Returns the resulting target list. + * + */ +List * +new_unsorted_tlist(List *targetlist) +{ + List *new_targetlist = (List*)copyObject ((Node*)targetlist); + List *x = NIL; + + foreach (x, new_targetlist) { + TargetEntry *tle = (TargetEntry *)lfirst(x); + tle->resdom->reskey = 0; + tle->resdom->reskeyop = (Oid)0; + } + return(new_targetlist); +} + +/* + * copy-vars-- + * Replaces the var nodes in the first target list with those from + * the second target list. The two target lists are assumed to be + * identical except their actual resdoms and vars are different. + * + * 'target' is the target list to be replaced + * 'source' is the target list to be copied + * + * Returns a new target list. + * + */ +List * +copy_vars(List *target, List *source) +{ + List *result = NIL; + List *src = NIL; + List *dest = NIL; + + for ( src = source, dest = target; src != NIL && + dest != NIL; src = lnext(src), dest = lnext(dest)) { + TargetEntry *temp = MakeTLE(((TargetEntry *)lfirst(dest))->resdom, + (Node*)get_expr(lfirst(src))); + result = lappend(result,temp); + } + return(result); +} + +/* + * flatten-tlist-- + * Create a target list that only contains unique variables. + * + * + * 'tlist' is the current target list + * + * Returns the "flattened" new target list. + * + */ +List * +flatten_tlist(List *tlist) +{ + int last_resdomno = 1; + List *new_tlist = NIL; + List *tlist_vars = NIL; + List *temp; + + foreach (temp, tlist) { + TargetEntry *temp_entry = NULL; + List *vars; + + temp_entry = lfirst(temp); + vars = pull_var_clause((Node*)get_expr(temp_entry)); + if(vars != NULL) { + tlist_vars = nconc(tlist_vars, vars); + } + } + + foreach (temp, tlist_vars) { + Var *var = lfirst(temp); + if (!(tlist_member(var, new_tlist))) { + Resdom *r; + + r = makeResdom(last_resdomno, + var->vartype, + get_typlen(var->vartype), + NULL, + (Index)0, + (Oid)0, + 0); + last_resdomno++; + new_tlist = lappend(new_tlist, MakeTLE (r, (Node*)var)); + } + } + + return new_tlist; +} + +/* + * flatten-tlist-vars-- + * Redoes the target list of a query with no nested attributes by + * replacing vars within computational expressions with vars from + * the 'flattened' target list of the query. + * + * 'full-tlist' is the actual target list + * 'flat-tlist' is the flattened (var-only) target list + * + * Returns the modified actual target list. + * + */ +List * +flatten_tlist_vars(List *full_tlist, List *flat_tlist) +{ + List *x = NIL; + List *result = NIL; + + foreach(x,full_tlist) { + TargetEntry *tle= lfirst(x); + result = + lappend(result, + MakeTLE(tle->resdom, + flatten_tlistentry((Node*)get_expr(tle), + flat_tlist))); + } + + return(result); +} + +/* + * flatten-tlistentry-- + * Replaces vars within a target list entry with vars from a flattened + * target list. + * + * 'tlistentry' is the target list entry to be modified + * 'flat-tlist' is the flattened target list + * + * Returns the (modified) target_list entry from the target list. + * + */ +static Node * +flatten_tlistentry(Node *tlistentry, List *flat_tlist) +{ + if (tlistentry==NULL) { + + return NULL; + + } else if (IsA (tlistentry,Var)) { + + return + ((Node *)get_expr(match_varid((Var*)tlistentry, + flat_tlist))); + } else if (IsA (tlistentry,Iter)) { + + ((Iter*)tlistentry)->iterexpr = + flatten_tlistentry((Node*)((Iter*)tlistentry)->iterexpr, + flat_tlist); + return tlistentry; + + } else if (single_node(tlistentry)) { + + return tlistentry; + + } else if (is_funcclause (tlistentry)) { + Expr *expr = (Expr*)tlistentry; + List *temp_result = NIL; + List *elt = NIL; + + foreach(elt, expr->args) + temp_result = lappend(temp_result, + flatten_tlistentry(lfirst(elt),flat_tlist)); + + return + ((Node *)make_funcclause((Func*)expr->oper, temp_result)); + + } else if (IsA(tlistentry,Aggreg)) { + + return tlistentry; + + } else if (IsA(tlistentry,ArrayRef)) { + ArrayRef *aref = (ArrayRef *)tlistentry; + List *temp = NIL; + List *elt = NIL; + + foreach(elt, aref->refupperindexpr) + temp = lappend(temp, flatten_tlistentry(lfirst(elt), flat_tlist)); + aref->refupperindexpr = temp; + + temp = NIL; + foreach(elt, aref->reflowerindexpr) + temp = lappend(temp, flatten_tlistentry(lfirst(elt), flat_tlist)); + aref->reflowerindexpr = temp; + + aref->refexpr = + flatten_tlistentry(aref->refexpr, flat_tlist); + + aref->refassgnexpr = + flatten_tlistentry(aref->refassgnexpr, flat_tlist); + + return tlistentry; + } else { + Expr *expr = (Expr*)tlistentry; + Var *left = + (Var*)flatten_tlistentry((Node*)get_leftop(expr), + flat_tlist); + Var *right = + (Var*)flatten_tlistentry((Node*)get_rightop(expr), + flat_tlist); + + return((Node *) + make_opclause((Oper*)expr->oper, left, right)); + } +} + + +TargetEntry * +MakeTLE(Resdom *resdom, Node *expr) +{ + TargetEntry *rt = makeNode(TargetEntry); + + rt->resdom = resdom; + rt->expr = expr; + return rt; +} + +Var * +get_expr(TargetEntry *tle) +{ + Assert(tle!=NULL); + Assert(tle->expr!=NULL); + + return ((Var *)tle->expr); +} + + +/***************************************************************************** + * + *****************************************************************************/ + +/* + * AddGroupAttrToTlist - + * append the group attribute to the target list if it's not already + * in there. + */ +void +AddGroupAttrToTlist(List *tlist, List *grpCl) +{ + List *gl; + int last_resdomno = length(tlist) + 1; + + foreach (gl, grpCl) { + GroupClause *gc = (GroupClause*)lfirst(gl); + Var *var = gc->grpAttr; + + if (!(tlist_member(var, tlist))) { + Resdom *r; + + r = makeResdom(last_resdomno, + var->vartype, + get_typlen(var->vartype), + NULL, + (Index)0, + (Oid)0, + 0); + last_resdomno++; + tlist = lappend(tlist, MakeTLE(r, (Node*)var)); + } + } +} + +/* was ExecTargetListLength() in execQual.c, + moved here to reduce dependencies on the executor module */ +int +exec_tlist_length(List *targetlist) +{ + int len; + List *tl; + TargetEntry *curTle; + + len = 0; + foreach (tl, targetlist) { + curTle = lfirst(tl); + + if (curTle->resdom != NULL) + len++; + } + return len; +} + + diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c new file mode 100644 index 00000000000..b4f8436c775 --- /dev/null +++ b/src/backend/optimizer/util/var.c @@ -0,0 +1,189 @@ +/*------------------------------------------------------------------------- + * + * var.c-- + * Var node manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.1.1.1 1996/07/09 06:21:39 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "nodes/primnodes.h" +#include "nodes/nodeFuncs.h" + +#include "optimizer/internal.h" +#include "optimizer/clauses.h" +#include "optimizer/var.h" + +#include "parser/parsetree.h" + +/* + * find_varnos + * + * Descends down part of a parsetree (qual or tlist), + * + * XXX assumes varno's are always integers, which shouldn't be true... + * (though it currently is, see primnodes.h) + */ +List * +pull_varnos(Node *me) +{ + List *i, *result = NIL; + + if (me == NULL) + return (NIL); + + switch (nodeTag(me)) { + case T_List: + foreach (i, (List*)me) { + result = nconc(result, pull_varnos(lfirst(i))); + } + break; + case T_ArrayRef: + foreach (i, ((ArrayRef*) me)->refupperindexpr) + result = nconc(result, pull_varnos(lfirst(i))); + foreach (i, ((ArrayRef*) me)->reflowerindexpr) + result = nconc(result, pull_varnos(lfirst(i))); + result = nconc(result, pull_varnos(((ArrayRef*) me)->refassgnexpr)); + break; + case T_Var: + result = lconsi(((Var*) me)->varno, NIL); + break; + default: + break; + } + return(result); +} + +/* + * contain_var_clause-- + * Recursively find var nodes from a clause by pulling vars from the + * left and right operands of the clause. + * + * Returns true if any varnode found. + */ +bool contain_var_clause(Node *clause) +{ + if (clause==NULL) + return FALSE; + else if (IsA(clause,Var)) + return TRUE; + else if (IsA(clause,Iter)) + return contain_var_clause(((Iter*)clause)->iterexpr); + else if (single_node(clause)) + return FALSE; + else if (or_clause(clause)) { + List *temp; + + foreach (temp, ((Expr*)clause)->args) { + if (contain_var_clause(lfirst(temp))) + return TRUE; + } + return FALSE; + } else if (is_funcclause (clause)) { + List *temp; + + foreach(temp, ((Expr *)clause)->args) { + if (contain_var_clause(lfirst(temp))) + return TRUE; + } + return FALSE; + } else if (IsA(clause,ArrayRef)) { + List *temp; + + foreach(temp, ((ArrayRef*)clause)->refupperindexpr) { + if (contain_var_clause(lfirst(temp))) + return TRUE; + } + foreach(temp, ((ArrayRef*)clause)->reflowerindexpr) { + if (contain_var_clause(lfirst(temp))) + return TRUE; + } + if (contain_var_clause(((ArrayRef*)clause)->refexpr)) + return TRUE; + if (contain_var_clause(((ArrayRef*)clause)->refassgnexpr)) + return TRUE; + return FALSE; + } else if (not_clause(clause)) + return contain_var_clause((Node*)get_notclausearg((Expr*)clause)); + else if (is_opclause(clause)) + return (contain_var_clause((Node*)get_leftop((Expr*)clause)) || + contain_var_clause((Node*)get_rightop((Expr*)clause))); + + return FALSE; +} + +/* + * pull_var_clause-- + * Recursively pulls all var nodes from a clause by pulling vars from the + * left and right operands of the clause. + * + * Returns list of varnodes found. + */ +List * +pull_var_clause(Node *clause) +{ + List *retval = NIL; + + if (clause==NULL) + return(NIL); + else if (IsA(clause,Var)) + retval = lcons(clause,NIL); + else if (IsA(clause,Iter)) + retval = pull_var_clause(((Iter*)clause)->iterexpr); + else if (single_node(clause)) + retval = NIL; + else if (or_clause(clause)) { + List *temp; + + foreach (temp, ((Expr*)clause)->args) + retval = nconc(retval, pull_var_clause(lfirst(temp))); + } else if (is_funcclause (clause)) { + List *temp; + + foreach(temp, ((Expr *)clause)->args) + retval = nconc (retval,pull_var_clause(lfirst(temp))); + } else if (IsA(clause,Aggreg)) { + retval = pull_var_clause(((Aggreg*)clause)->target); + } else if (IsA(clause,ArrayRef)) { + List *temp; + + foreach(temp, ((ArrayRef*)clause)->refupperindexpr) + retval = nconc (retval,pull_var_clause(lfirst(temp))); + foreach(temp, ((ArrayRef*)clause)->reflowerindexpr) + retval = nconc (retval,pull_var_clause(lfirst(temp))); + retval = nconc(retval, + pull_var_clause(((ArrayRef*)clause)->refexpr)); + retval = nconc(retval, + pull_var_clause(((ArrayRef*)clause)->refassgnexpr)); + } else if (not_clause(clause)) + retval = pull_var_clause((Node*)get_notclausearg((Expr*)clause)); + else if (is_opclause(clause)) + retval = nconc(pull_var_clause((Node*)get_leftop((Expr*)clause)), + pull_var_clause((Node*)get_rightop((Expr*)clause))); + else + retval = NIL; + + return (retval); +} + +/* + * var_equal + * + * Returns t iff two var nodes correspond to the same attribute. + */ +bool +var_equal(Var *var1, Var *var2) +{ + if (IsA (var1,Var) && IsA (var2,Var) && + (((Var*)var1)->varno == ((Var*)var2)->varno) && + (((Var*)var1)->vartype == ((Var*)var2)->vartype) && + (((Var*)var1)->varattno == ((Var*)var2)->varattno)) { + + return(true); + } else + return(false); +} diff --git a/src/backend/optimizer/var.h b/src/backend/optimizer/var.h new file mode 100644 index 00000000000..fdcf1ea647b --- /dev/null +++ b/src/backend/optimizer/var.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * var.h-- + * prototypes for var.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: var.h,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef VAR_H +#define VAR_H + +extern List *pull_varnos(Node *me); +extern bool contain_var_clause(Node *clause); +extern List *pull_var_clause(Node *clause); +extern bool var_equal(Var *var1, Var *var2); + +#endif /* VAR_H */ diff --git a/src/backend/optimizer/xfunc.h b/src/backend/optimizer/xfunc.h new file mode 100644 index 00000000000..a3ee1b99cc2 --- /dev/null +++ b/src/backend/optimizer/xfunc.h @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------- + * + * xfunc.h-- + * prototypes for xfunc.c and predmig.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: xfunc.h,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef XFUNC_H +#define XFUNC_H + +#include "nodes/relation.h" + +/* command line arg flags */ +#define XFUNC_OFF -1 /* do no optimization of expensive preds */ +#define XFUNC_NOR 2 /* do no optimization of OR clauses */ +#define XFUNC_NOPULL 4 /* never pull restrictions above joins */ +#define XFUNC_NOPM 8 /* don't do predicate migration */ +#define XFUNC_WAIT 16 /* don't do pullup until predicate migration */ +#define XFUNC_PULLALL 32 /* pull all expensive restrictions up, always */ + +/* constants for local and join predicates */ +#define XFUNC_LOCPRD 1 +#define XFUNC_JOINPRD 2 +#define XFUNC_UNKNOWN 0 + +extern int XfuncMode; /* defined in tcop/postgres.c */ + +/* defaults for function attributes used for expensive function calculations */ +#define BYTE_PCT 100 +#define PERBYTE_CPU 0 +#define PERCALL_CPU 0 +#define OUTIN_RATIO 100 + +/* default width assumed for variable length attributes */ +#define VARLEN_DEFAULT 128; + +/* Macro to get group rank out of group cost and group sel */ +#define get_grouprank(a) ((get_groupsel(a) - 1) / get_groupcost(a)) + +/* Macro to see if a path node is actually a Join */ +#define is_join(pathnode) (length(get_relids(get_parent(pathnode))) > 1 ? 1 : 0) + +/* function prototypes from planner/path/xfunc.c */ +extern void xfunc_trypullup(Rel *rel); +extern int xfunc_shouldpull(Path *childpath, JoinPath *parentpath, + int whichchild, CInfo *maxcinfopt); +extern CInfo *xfunc_pullup(Path *childpath, JoinPath *parentpath, CInfo *cinfo, + int whichchild, int clausetype); +extern Cost xfunc_rank(Expr *clause); +extern Cost xfunc_expense(Query* queryInfo, Expr *clause); +extern Cost xfunc_join_expense(JoinPath *path, int whichchild); +extern Cost xfunc_local_expense(Expr *clause); +extern Cost xfunc_func_expense(Expr *node, List *args); +extern int xfunc_width(Expr *clause); +/* static, moved to xfunc.c */ +/* extern int xfunc_card_unreferenced(Expr *clause, Relid referenced); */ +extern int xfunc_card_product(Relid relids); +extern List *xfunc_find_references(List *clause); +extern List *xfunc_primary_join(JoinPath *pathnode); +extern Cost xfunc_get_path_cost(Path *pathnode); +extern Cost xfunc_total_path_cost(JoinPath *pathnode); +extern Cost xfunc_expense_per_tuple(JoinPath *joinnode, int whichchild); +extern void xfunc_fixvars(Expr *clause, Rel *rel, int varno); +extern int xfunc_cinfo_compare(void *arg1, void *arg2); +extern int xfunc_clause_compare(void *arg1, void *arg2); +extern void xfunc_disjunct_sort(List *clause_list); +extern int xfunc_disjunct_compare(void *arg1, void *arg2); +extern int xfunc_func_width(RegProcedure funcid, List *args); +extern int xfunc_tuple_width(Relation rd); +extern int xfunc_num_join_clauses(JoinPath *path); +extern List *xfunc_LispRemove(List *foo, List *bar); +extern bool xfunc_copyrel(Rel *from, Rel **to); + +/* + * function prototypes for path/predmig.c + */ +extern bool xfunc_do_predmig(Path root); + +#endif /* XFUNC_H */ |