diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index cc466f5ae006ccae980b06423c06b9a16b8be7cc..1a78769e8f2d768b86ebd5fc825805249ef5ac45 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.227 2004/01/05 18:04:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.228 2004/01/05 23:39:53 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -968,8 +968,9 @@ _outIndexPath(StringInfo str, IndexPath *node) _outPathInfo(str, (Path *) node); WRITE_NODE_FIELD(indexinfo); - WRITE_NODE_FIELD(indexqual); - WRITE_NODE_FIELD(indexjoinclauses); + WRITE_NODE_FIELD(indexclauses); + WRITE_NODE_FIELD(indexquals); + WRITE_BOOL_FIELD(isjoininner); WRITE_ENUM_FIELD(indexscandir, ScanDirection); WRITE_FLOAT_FIELD(rows, "%.0f"); } diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index fa7dcdc9740d8af1dfdaacda324468f9bebd19be..7353e73462b01bc013d5eb9c519a69b68cc447e7 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -49,7 +49,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.120 2004/01/05 05:07:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.121 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -238,6 +238,9 @@ cost_nonsequential_access(double relpages) * Any additional quals evaluated as qpquals may reduce the number of returned * tuples, but they won't reduce the number of tuples we have to fetch from * the table, so they don't reduce the scan cost. + * + * NOTE: as of 7.5, indexQuals is a list of RestrictInfo nodes, where formerly + * it was a list of bare clause expressions. */ void cost_index(Path *path, Query *root, diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 83d129d0c9cff737b54f9960de1eea18916aa382..da11d7f86d06871a28a42dce85ad0c5c2c7ff7b7 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.154 2004/01/05 05:07:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.155 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -76,7 +76,7 @@ static bool match_index_to_operand(Node *operand, int indexcol, RelOptInfo *rel, IndexOptInfo *index); static bool match_special_index_operator(Expr *clause, Oid opclass, bool indexkey_on_left); -static List *expand_indexqual_condition(Expr *clause, Oid opclass); +static List *expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass); static List *prefix_quals(Node *leftop, Oid opclass, Const *prefix, Pattern_Prefix_Status pstatus); static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, @@ -1418,8 +1418,7 @@ make_innerjoin_index_path(Query *root, { IndexPath *pathnode = makeNode(IndexPath); List *indexquals, - *allclauses, - *l; + *allclauses; /* XXX perhaps this code should be merged with create_index_path? */ @@ -1433,28 +1432,21 @@ make_innerjoin_index_path(Query *root, */ pathnode->path.pathkeys = NIL; - /* Convert RestrictInfo nodes to indexquals the executor can handle */ + /* Convert clauses to indexquals the executor can handle */ indexquals = expand_indexqual_conditions(index, clausegroups); - /* - * Also make a flattened list of the RestrictInfo nodes; createplan.c - * will need this later. We assume here that we can destructively - * modify the passed-in clausegroups list structure. - */ - allclauses = NIL; - foreach(l, clausegroups) - { - /* nconc okay here since same clause couldn't be in two sublists */ - allclauses = nconc(allclauses, (List *) lfirst(l)); - } + /* Flatten the clausegroups list to produce indexclauses list */ + allclauses = flatten_clausegroups_list(clausegroups); /* * Note that we are making a pathnode for a single-scan indexscan; - * therefore, indexinfo and indexqual should be single-element lists. + * therefore, indexinfo etc should be single-element lists. */ pathnode->indexinfo = makeList1(index); - pathnode->indexqual = makeList1(indexquals); - pathnode->indexjoinclauses = makeList1(allclauses); + pathnode->indexclauses = makeList1(allclauses); + pathnode->indexquals = makeList1(indexquals); + + pathnode->isjoininner = true; /* We don't actually care what order the index scans in ... */ pathnode->indexscandir = NoMovementScanDirection; @@ -1489,6 +1481,61 @@ make_innerjoin_index_path(Query *root, return (Path *) pathnode; } +/* + * flatten_clausegroups_list + * Given a list of lists of RestrictInfos, flatten it to a list + * of RestrictInfos. + * + * This is used to flatten out the result of group_clauses_by_indexkey() + * or one of its sibling routines, to produce an indexclauses list. + */ +List * +flatten_clausegroups_list(List *clausegroups) +{ + List *allclauses = NIL; + List *l; + + foreach(l, clausegroups) + { + allclauses = nconc(allclauses, listCopy((List *) lfirst(l))); + } + return allclauses; +} + +/* + * make_expr_from_indexclauses() + * Given an indexclauses structure, produce an ordinary boolean expression. + * + * This consists of stripping out the RestrictInfo nodes and inserting + * explicit AND and OR nodes as needed. There's not much to it, but + * the functionality is needed in a few places, so centralize the logic. + */ +Expr * +make_expr_from_indexclauses(List *indexclauses) +{ + List *orclauses = NIL; + List *orlist; + + /* There's no such thing as an indexpath with zero scans */ + Assert(indexclauses != NIL); + + foreach(orlist, indexclauses) + { + List *andlist = (List *) lfirst(orlist); + + /* Strip RestrictInfos */ + andlist = get_actual_clauses(andlist); + /* Insert AND node if needed, and add to orclauses list */ + orclauses = lappend(orclauses, make_ands_explicit(andlist)); + } + + if (length(orclauses) > 1) + return make_orclause(orclauses); + else + return (Expr *) lfirst(orclauses); +} + + /**************************************************************************** * ---- ROUTINES TO CHECK OPERANDS ---- ****************************************************************************/ @@ -1799,8 +1846,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); FastConc(&resultquals, - expand_indexqual_condition(rinfo->clause, - curClass)); + expand_indexqual_condition(rinfo, curClass)); } clausegroups = lnext(clausegroups); @@ -1816,10 +1862,13 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) /* * expand_indexqual_condition --- expand a single indexqual condition + * + * The input is a single RestrictInfo, the output a list of RestrictInfos */ static List * -expand_indexqual_condition(Expr *clause, Oid opclass) +expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass) { + Expr *clause = rinfo->clause; /* we know these will succeed */ Node *leftop = get_leftop(clause); Node *rightop = get_rightop(clause); @@ -1883,7 +1932,7 @@ expand_indexqual_condition(Expr *clause, Oid opclass) break; default: - result = makeList1(clause); + result = makeList1(rinfo); break; } @@ -1978,7 +2027,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no = operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = makeList1(expr); + result = makeList1(make_restrictinfo(expr, true, true)); return result; } @@ -1993,7 +2042,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no >= operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = makeList1(expr); + result = makeList1(make_restrictinfo(expr, true, true)); /*------- * If we can create a string larger than the prefix, we can say @@ -2009,7 +2058,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no < operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) greaterstr); - result = lappend(result, expr); + result = lappend(result, make_restrictinfo(expr, true, true)); } return result; @@ -2080,7 +2129,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr1right, false, false)); - result = makeList1(expr); + result = makeList1(make_restrictinfo(expr, true, true)); /* create clause "key <= network_scan_last( rightop )" */ @@ -2095,7 +2144,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr2right, false, false)); - result = lappend(result, expr); + result = lappend(result, make_restrictinfo(expr, true, true)); return result; } diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index 02e486ec35cf9a9fa5e0184b71502c5f6918e639..9722b69965dfb8e85119a3f254df10920b5fe84c 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.56 2004/01/05 05:07:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.57 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,7 +28,8 @@ static bool best_or_subclause_index(Query *root, RelOptInfo *rel, Expr *subclause, IndexOptInfo **retIndexInfo, - List **retIndexQual, + List **retIndexClauses, + List **retIndexQuals, Cost *retStartupCost, Cost *retTotalCost); @@ -95,9 +96,7 @@ create_or_index_quals(Query *root, RelOptInfo *rel) { IndexPath *bestpath = NULL; RestrictInfo *bestrinfo = NULL; - FastList orclauses; - List *orclause; - Expr *indxqual_or_expr; + List *newrinfos; RestrictInfo *or_rinfo; Selectivity or_selec, orig_selec; @@ -145,20 +144,14 @@ create_or_index_quals(Query *root, RelOptInfo *rel) return false; /* - * Build an expression representation of the indexqual, expanding - * the implicit OR and AND semantics of the first- and - * second-level lists. + * Convert the indexclauses structure to a RestrictInfo tree, + * and add it to the rel's restriction list. */ - FastListInit(&orclauses); - foreach(orclause, bestpath->indexqual) - FastAppend(&orclauses, make_ands_explicit(lfirst(orclause))); - indxqual_or_expr = make_orclause(FastListValue(&orclauses)); - - /* - * And add it to the rel's restriction list. - */ - or_rinfo = make_restrictinfo(indxqual_or_expr, true, true); - rel->baserestrictinfo = lappend(rel->baserestrictinfo, or_rinfo); + newrinfos = make_restrictinfo_from_indexclauses(bestpath->indexclauses, + true, true); + Assert(length(newrinfos) == 1); + or_rinfo = (RestrictInfo *) lfirst(newrinfos); + rel->baserestrictinfo = nconc(rel->baserestrictinfo, newrinfos); /* * Adjust the original OR clause's cached selectivity to compensate @@ -251,6 +244,7 @@ best_or_subclause_indexes(Query *root, List *subclauses) { FastList infos; + FastList clauses; FastList quals; Cost path_startup_cost; Cost path_total_cost; @@ -258,6 +252,7 @@ best_or_subclause_indexes(Query *root, IndexPath *pathnode; FastListInit(&infos); + FastListInit(&clauses); FastListInit(&quals); path_startup_cost = 0; path_total_cost = 0; @@ -267,17 +262,20 @@ best_or_subclause_indexes(Query *root, { Expr *subclause = lfirst(slist); IndexOptInfo *best_indexinfo; - List *best_indexqual; + List *best_indexclauses; + List *best_indexquals; Cost best_startup_cost; Cost best_total_cost; if (!best_or_subclause_index(root, rel, subclause, - &best_indexinfo, &best_indexqual, + &best_indexinfo, + &best_indexclauses, &best_indexquals, &best_startup_cost, &best_total_cost)) return NULL; /* failed to match this subclause */ FastAppend(&infos, best_indexinfo); - FastAppend(&quals, best_indexqual); + FastAppend(&clauses, best_indexclauses); + FastAppend(&quals, best_indexquals); /* * Path startup_cost is the startup cost for the first index scan only; * startup costs for later scans will be paid later on, so they just @@ -306,10 +304,11 @@ best_or_subclause_indexes(Query *root, pathnode->path.pathkeys = NIL; pathnode->indexinfo = FastListValue(&infos); - pathnode->indexqual = FastListValue(&quals); + pathnode->indexclauses = FastListValue(&clauses); + pathnode->indexquals = FastListValue(&quals); /* It's not an innerjoin path. */ - pathnode->indexjoinclauses = NIL; + pathnode->isjoininner = false; /* We don't actually care what order the index scans in. */ pathnode->indexscandir = NoMovementScanDirection; @@ -336,7 +335,8 @@ best_or_subclause_indexes(Query *root, * 'subclause' is the OR subclause being considered * * '*retIndexInfo' gets the IndexOptInfo of the best index - * '*retIndexQual' gets a list of the indexqual conditions for the best index + * '*retIndexClauses' gets a list of the index clauses for the best index + * '*retIndexQuals' gets a list of the expanded indexquals for the best index * '*retStartupCost' gets the startup cost of a scan with that index * '*retTotalCost' gets the total cost of a scan with that index */ @@ -345,7 +345,8 @@ best_or_subclause_index(Query *root, RelOptInfo *rel, Expr *subclause, IndexOptInfo **retIndexInfo, /* return value */ - List **retIndexQual, /* return value */ + List **retIndexClauses, /* return value */ + List **retIndexQuals, /* return value */ Cost *retStartupCost, /* return value */ Cost *retTotalCost) /* return value */ { @@ -355,7 +356,7 @@ best_or_subclause_index(Query *root, foreach(ilist, rel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); - List *qualrinfos; + List *indexclauses; List *indexquals; Path subclause_path; @@ -364,21 +365,22 @@ best_or_subclause_index(Query *root, continue; /* Collect index clauses usable with this index */ - qualrinfos = group_clauses_by_indexkey_for_or(rel, index, subclause); + indexclauses = group_clauses_by_indexkey_for_or(rel, index, subclause); /* Ignore index if it doesn't match the subclause at all */ - if (qualrinfos == NIL) + if (indexclauses == NIL) continue; - /* Convert RestrictInfo nodes to indexquals the executor can handle */ - indexquals = expand_indexqual_conditions(index, qualrinfos); + /* Convert clauses to indexquals the executor can handle */ + indexquals = expand_indexqual_conditions(index, indexclauses); cost_index(&subclause_path, root, rel, index, indexquals, false); if (!found || subclause_path.total_cost < *retTotalCost) { *retIndexInfo = index; - *retIndexQual = indexquals; + *retIndexClauses = flatten_clausegroups_list(indexclauses); + *retIndexQuals = indexquals; *retStartupCost = subclause_path.startup_cost; *retTotalCost = subclause_path.total_cost; found = true; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index ace9029ce686c9c0f934371c685b59e2f8e0f858..591cf47f8c63723212c2d0268858b6521395be51 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.163 2004/01/05 18:04:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.164 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,15 +44,15 @@ static Append *create_append_plan(Query *root, AppendPath *best_path); static Result *create_result_plan(Query *root, ResultPath *best_path); static Material *create_material_plan(Query *root, MaterialPath *best_path); static Plan *create_unique_plan(Query *root, UniquePath *best_path); -static SeqScan *create_seqscan_plan(Path *best_path, List *tlist, - List *scan_clauses); +static SeqScan *create_seqscan_plan(Query *root, Path *best_path, + List *tlist, List *scan_clauses); static IndexScan *create_indexscan_plan(Query *root, IndexPath *best_path, List *tlist, List *scan_clauses); -static TidScan *create_tidscan_plan(TidPath *best_path, List *tlist, - List *scan_clauses); -static SubqueryScan *create_subqueryscan_plan(Path *best_path, +static TidScan *create_tidscan_plan(Query *root, TidPath *best_path, + List *tlist, List *scan_clauses); +static SubqueryScan *create_subqueryscan_plan(Query *root, Path *best_path, List *tlist, List *scan_clauses); -static FunctionScan *create_functionscan_plan(Path *best_path, +static FunctionScan *create_functionscan_plan(Query *root, Path *best_path, List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(Query *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); @@ -219,15 +219,13 @@ create_scan_plan(Query *root, Path *best_path) * Extract the relevant restriction clauses from the parent relation; * the executor must apply all these restrictions during the scan. */ - scan_clauses = get_actual_clauses(rel->baserestrictinfo); - - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); + scan_clauses = rel->baserestrictinfo; switch (best_path->pathtype) { case T_SeqScan: - plan = (Scan *) create_seqscan_plan(best_path, + plan = (Scan *) create_seqscan_plan(root, + best_path, tlist, scan_clauses); break; @@ -240,19 +238,22 @@ create_scan_plan(Query *root, Path *best_path) break; case T_TidScan: - plan = (Scan *) create_tidscan_plan((TidPath *) best_path, + plan = (Scan *) create_tidscan_plan(root, + (TidPath *) best_path, tlist, scan_clauses); break; case T_SubqueryScan: - plan = (Scan *) create_subqueryscan_plan(best_path, + plan = (Scan *) create_subqueryscan_plan(root, + best_path, tlist, scan_clauses); break; case T_FunctionScan: - plan = (Scan *) create_functionscan_plan(best_path, + plan = (Scan *) create_functionscan_plan(root, + best_path, tlist, scan_clauses); break; @@ -667,7 +668,8 @@ create_unique_plan(Query *root, UniquePath *best_path) * with restriction clauses 'scan_clauses' and targetlist 'tlist'. */ static SeqScan * -create_seqscan_plan(Path *best_path, List *tlist, List *scan_clauses) +create_seqscan_plan(Query *root, Path *best_path, + List *tlist, List *scan_clauses) { SeqScan *scan_plan; Index scan_relid = best_path->parent->relid; @@ -676,6 +678,12 @@ create_seqscan_plan(Path *best_path, List *tlist, List *scan_clauses) Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_RELATION); + /* Reduce RestrictInfo list to bare expressions */ + scan_clauses = get_actual_clauses(scan_clauses); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan = make_seqscan(tlist, scan_clauses, scan_relid); @@ -690,9 +698,9 @@ create_seqscan_plan(Path *best_path, List *tlist, List *scan_clauses) * Returns a indexscan plan for the base relation scanned by 'best_path' * with restriction clauses 'scan_clauses' and targetlist 'tlist'. * - * The indexqual of the path contains a sublist of implicitly-ANDed qual - * conditions for each scan of the index(es); if there is more than one - * scan then the retrieved tuple sets are ORed together. The indexqual + * The indexquals list of the path contains a sublist of implicitly-ANDed + * qual conditions for each scan of the index(es); if there is more than one + * scan then the retrieved tuple sets are ORed together. The indexquals * and indexinfo lists must have the same length, ie, the number of scans * that will occur. Note it is possible for a qual condition sublist * to be empty --- then no index restrictions will be applied during that @@ -704,16 +712,17 @@ create_indexscan_plan(Query *root, List *tlist, List *scan_clauses) { - List *indxqual = best_path->indexqual; + List *indxquals = best_path->indexquals; Index baserelid = best_path->path.parent->relid; List *qpqual; Expr *indxqual_or_expr = NULL; - List *fixed_indxqual; - List *recheck_indxqual; + List *stripped_indxquals; + List *fixed_indxquals; + List *recheck_indxquals; List *indxstrategy; List *indxsubtype; FastList indexids; - List *ixinfo; + List *i; IndexScan *scan_plan; /* it should be a base rel... */ @@ -721,64 +730,94 @@ create_indexscan_plan(Query *root, Assert(best_path->path.parent->rtekind == RTE_RELATION); /* - * Build list of index OIDs. + * If this is a innerjoin scan, the indexclauses will contain join + * clauses that are not present in scan_clauses (since the passed-in + * value is just the rel's baserestrictinfo list). We must add these + * clauses to scan_clauses to ensure they get checked. In most cases + * we will remove the join clauses again below, but if a join clause + * contains a lossy or special operator, we need to make sure it gets + * into scan_clauses. */ + if (best_path->isjoininner) + { + /* + * We don't currently support OR indexscans in joins, so we only + * need to worry about the plain AND case. Also, pointer comparison + * should be enough to determine RestrictInfo matches. + */ + Assert(length(best_path->indexclauses) == 1); + scan_clauses = set_ptrUnion(scan_clauses, + (List *) lfirst(best_path->indexclauses)); + } + + /* Reduce RestrictInfo list to bare expressions */ + scan_clauses = get_actual_clauses(scan_clauses); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + + /* Build list of index OIDs */ FastListInit(&indexids); - foreach(ixinfo, best_path->indexinfo) + foreach(i, best_path->indexinfo) { - IndexOptInfo *index = (IndexOptInfo *) lfirst(ixinfo); + IndexOptInfo *index = (IndexOptInfo *) lfirst(i); FastAppendo(&indexids, index->indexoid); } + /* + * Build "stripped" indexquals structure (no RestrictInfos) to pass to + * executor as indxqualorig + */ + stripped_indxquals = NIL; + foreach(i, indxquals) + { + List *andlist = (List *) lfirst(i); + + stripped_indxquals = lappend(stripped_indxquals, + get_actual_clauses(andlist)); + } + /* * The qpqual list must contain all restrictions not automatically - * handled by the index. Normally the predicates in the indxqual are + * handled by the index. Normally the predicates in the indexquals are * checked fully by the index, but if the index is "lossy" for a * particular operator (as signaled by the amopreqcheck flag in * pg_amop), then we need to double-check that predicate in qpqual, * because the index may return more tuples than match the predicate. * * Since the indexquals were generated from the restriction clauses given - * by scan_clauses, there will normally be some duplications between - * the lists. We get rid of the duplicates, then add back if lossy. + * by scan_clauses, there will normally be duplications between the lists. + * We get rid of the duplicates, then add back if lossy. */ - if (length(indxqual) > 1) + if (length(indxquals) > 1) { /* * Build an expression representation of the indexqual, expanding * the implicit OR and AND semantics of the first- and * second-level lists. */ - FastList orclauses; - List *orclause; - - FastListInit(&orclauses); - foreach(orclause, indxqual) - FastAppend(&orclauses, make_ands_explicit(lfirst(orclause))); - indxqual_or_expr = make_orclause(FastListValue(&orclauses)); - + indxqual_or_expr = make_expr_from_indexclauses(indxquals); qpqual = set_difference(scan_clauses, makeList1(indxqual_or_expr)); } - else if (indxqual != NIL) + else { /* * Here, we can simply treat the first sublist as an independent * set of qual expressions, since there is no top-level OR * behavior. */ - qpqual = set_difference(scan_clauses, lfirst(indxqual)); + Assert(stripped_indxquals != NIL); + qpqual = set_difference(scan_clauses, lfirst(stripped_indxquals)); } - else - qpqual = scan_clauses; /* * The executor needs a copy with the indexkey on the left of each * clause and with index attr numbers substituted for table ones. This * pass also looks for "lossy" operators. */ - fix_indxqual_references(indxqual, best_path, - &fixed_indxqual, &recheck_indxqual, + fix_indxqual_references(indxquals, best_path, + &fixed_indxquals, &recheck_indxquals, &indxstrategy, &indxsubtype); /* @@ -786,10 +825,10 @@ create_indexscan_plan(Query *root, * appropriate qual clauses to the qpqual. When there is just one * indexscan being performed (ie, we have simple AND semantics), we * can just add the lossy clauses themselves to qpqual. If we have - * OR-of-ANDs, we'd better add the entire original indexqual to make + * OR-of-ANDs, we'd better add the entire original indexquals to make * sure that the semantics are correct. */ - if (recheck_indxqual != NIL) + if (recheck_indxquals != NIL) { if (indxqual_or_expr) { @@ -799,8 +838,8 @@ create_indexscan_plan(Query *root, else { /* Subroutine already copied quals, so just append to list */ - Assert(length(recheck_indxqual) == 1); - qpqual = nconc(qpqual, (List *) lfirst(recheck_indxqual)); + Assert(length(recheck_indxquals) == 1); + qpqual = nconc(qpqual, (List *) lfirst(recheck_indxquals)); } } @@ -809,8 +848,8 @@ create_indexscan_plan(Query *root, qpqual, baserelid, FastListValue(&indexids), - fixed_indxqual, - indxqual, + fixed_indxquals, + stripped_indxquals, indxstrategy, indxsubtype, best_path->indexscandir); @@ -828,7 +867,8 @@ create_indexscan_plan(Query *root, * with restriction clauses 'scan_clauses' and targetlist 'tlist'. */ static TidScan * -create_tidscan_plan(TidPath *best_path, List *tlist, List *scan_clauses) +create_tidscan_plan(Query *root, TidPath *best_path, + List *tlist, List *scan_clauses) { TidScan *scan_plan; Index scan_relid = best_path->path.parent->relid; @@ -837,6 +877,12 @@ create_tidscan_plan(TidPath *best_path, List *tlist, List *scan_clauses) Assert(scan_relid > 0); Assert(best_path->path.parent->rtekind == RTE_RELATION); + /* Reduce RestrictInfo list to bare expressions */ + scan_clauses = get_actual_clauses(scan_clauses); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan = make_tidscan(tlist, scan_clauses, scan_relid, @@ -853,7 +899,8 @@ create_tidscan_plan(TidPath *best_path, List *tlist, List *scan_clauses) * with restriction clauses 'scan_clauses' and targetlist 'tlist'. */ static SubqueryScan * -create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses) +create_subqueryscan_plan(Query *root, Path *best_path, + List *tlist, List *scan_clauses) { SubqueryScan *scan_plan; Index scan_relid = best_path->parent->relid; @@ -862,6 +909,12 @@ create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses) Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_SUBQUERY); + /* Reduce RestrictInfo list to bare expressions */ + scan_clauses = get_actual_clauses(scan_clauses); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan = make_subqueryscan(tlist, scan_clauses, scan_relid, @@ -878,7 +931,8 @@ create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses) * with restriction clauses 'scan_clauses' and targetlist 'tlist'. */ static FunctionScan * -create_functionscan_plan(Path *best_path, List *tlist, List *scan_clauses) +create_functionscan_plan(Query *root, Path *best_path, + List *tlist, List *scan_clauses) { FunctionScan *scan_plan; Index scan_relid = best_path->parent->relid; @@ -887,6 +941,12 @@ create_functionscan_plan(Path *best_path, List *tlist, List *scan_clauses) Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_FUNCTION); + /* Reduce RestrictInfo list to bare expressions */ + scan_clauses = get_actual_clauses(scan_clauses); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan = make_functionscan(tlist, scan_clauses, scan_relid); copy_path_costsize(&scan_plan->scan.plan, best_path); @@ -928,20 +988,19 @@ create_nestloop_plan(Query *root, * have caught this case because the join clauses would never have * been put in the same joininfo list. * - * This would be a waste of time if the indexpath was an ordinary - * indexpath and not a special innerjoin path. We will skip it in - * that case since indexjoinclauses is NIL in an ordinary - * indexpath. + * We can skip this if the index path is an ordinary indexpath and + * not a special innerjoin path. */ IndexPath *innerpath = (IndexPath *) best_path->innerjoinpath; - List *indexjoinclauses = innerpath->indexjoinclauses; + List *indexclauses = innerpath->indexclauses; - if (length(indexjoinclauses) == 1) /* single indexscan? */ + if (innerpath->isjoininner && + length(indexclauses) == 1) /* single indexscan? */ { joinrestrictclauses = select_nonredundant_join_clauses(root, joinrestrictclauses, - lfirst(indexjoinclauses), + lfirst(indexclauses), best_path->jointype); } } @@ -1138,7 +1197,8 @@ create_hashjoin_plan(Query *root, * Adjust indexqual clauses to the form the executor's indexqual * machinery needs, and check for recheckable (lossy) index conditions. * - * We have four tasks here: + * We have five tasks here: + * * Remove RestrictInfo nodes from the input clauses. * * Index keys must be represented by Var nodes with varattno set to the * index's attribute number, not the attribute number in the original rel. * * If the index key is on the right, commute the clause to put it on the @@ -1154,14 +1214,15 @@ create_hashjoin_plan(Query *root, * * Both the input list and the output lists have the form of lists of sublists * of qual clauses --- the top-level list has one entry for each indexscan - * to be performed. The semantics are OR-of-ANDs. + * to be performed. The semantics are OR-of-ANDs. Note however that the + * input list contains RestrictInfos, while the output lists do not. * * fixed_indexquals receives a modified copy of the indexqual list --- the * original is not changed. Note also that the copy shares no substructure * with the original; this is needed in case there is a subplan in it (we need * two separate copies of the subplan tree, or things will go awry). * - * recheck_indexquals similarly receives a full copy of whichever clauses + * recheck_indexquals similarly receives a copy of whichever clauses * need rechecking. * * indxstrategy receives a list of integer sublists of strategy numbers. @@ -1243,14 +1304,16 @@ fix_indxqual_sublist(List *indexqual, *subtype = NIL; foreach(i, indexqual) { - OpExpr *clause = (OpExpr *) lfirst(i); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + OpExpr *clause; OpExpr *newclause; - Relids leftvarnos; Oid opclass; int stratno; Oid stratsubtype; bool recheck; + Assert(IsA(rinfo, RestrictInfo)); + clause = (OpExpr *) rinfo->clause; if (!IsA(clause, OpExpr) || length(clause->args) != 2) elog(ERROR, "indexqual clause is not binary opclause"); @@ -1269,10 +1332,8 @@ fix_indxqual_sublist(List *indexqual, * the clause. The indexkey should be the side that refers to * (only) the base relation. */ - leftvarnos = pull_varnos((Node *) lfirst(newclause->args)); - if (!bms_equal(leftvarnos, baserelids)) + if (!bms_equal(rinfo->left_relids, baserelids)) CommuteClause(newclause); - bms_free(leftvarnos); /* * Now, determine which index attribute this is, change the diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index a2a6b35cd322714d7da55d45b73458ec67396244..5fe12f2b6d5687588386cac2db59d46a4d912656 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.98 2004/01/05 18:04:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.99 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -354,18 +354,22 @@ create_index_path(Query *root, pathnode->path.parent = rel; pathnode->path.pathkeys = pathkeys; - /* Convert RestrictInfo nodes to indexquals the executor can handle */ + /* Convert clauses to indexquals the executor can handle */ indexquals = expand_indexqual_conditions(index, restriction_clauses); + /* Flatten the clause-groups list to produce indexclauses list */ + restriction_clauses = flatten_clausegroups_list(restriction_clauses); + /* * We are making a pathnode for a single-scan indexscan; therefore, - * both indexinfo and indexqual should be single-element lists. + * indexinfo etc should be single-element lists. */ pathnode->indexinfo = makeList1(index); - pathnode->indexqual = makeList1(indexquals); + pathnode->indexclauses = makeList1(restriction_clauses); + pathnode->indexquals = makeList1(indexquals); /* It's not an innerjoin path. */ - pathnode->indexjoinclauses = NIL; + pathnode->isjoininner = false; pathnode->indexscandir = indexscandir; diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 0ab4a75c5c1ddf2a497b8dd1ecc1a3819ff3d23c..e2cc53cc692350244cf8c0ea878028b63b8bffae 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.24 2004/01/05 05:07:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.25 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,12 @@ #include "optimizer/var.h" -static Expr *make_sub_restrictinfos(Expr *clause, bool is_pushed_down, +static RestrictInfo *make_restrictinfo_internal(Expr *clause, + Expr *orclause, + bool is_pushed_down, + bool valid_everywhere); +static Expr *make_sub_restrictinfos(Expr *clause, + bool is_pushed_down, bool valid_everywhere); static bool join_clause_is_redundant(Query *root, RestrictInfo *rinfo, @@ -42,10 +47,89 @@ static bool join_clause_is_redundant(Query *root, */ RestrictInfo * make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere) +{ + Expr *orclause; + + /* + * If it's an OR clause, build a modified copy with RestrictInfos + * inserted above each subclause of the top-level AND/OR structure. + */ + if (or_clause((Node *) clause)) + { + orclause = make_sub_restrictinfos(clause, + is_pushed_down, + valid_everywhere); + } + else + { + /* Shouldn't be an AND clause, else flatten_andors messed up */ + Assert(!and_clause((Node *) clause)); + + orclause = NULL; + } + + return make_restrictinfo_internal(clause, orclause, + is_pushed_down, valid_everywhere); +} + +/* + * make_restrictinfo_from_indexclauses + * + * Given an indexclauses structure, convert to ordinary expression format + * and build RestrictInfo node(s). + * + * The result is a List since we might need to return multiple RestrictInfos. + * + * This could be done as make_restrictinfo(make_expr_from_indexclauses()), + * but if we did it that way then we would strip the original RestrictInfo + * nodes from the index clauses and be forced to build new ones. It's better + * to have a specialized routine that allows sharing of RestrictInfos. + */ +List * +make_restrictinfo_from_indexclauses(List *indexclauses, + bool is_pushed_down, + bool valid_everywhere) +{ + List *withris = NIL; + List *withoutris = NIL; + List *orlist; + + /* Empty list probably can't happen, but here's what to do */ + if (indexclauses == NIL) + return NIL; + /* If single indexscan, just return the ANDed clauses */ + if (lnext(indexclauses) == NIL) + return (List *) lfirst(indexclauses); + /* Else we need an OR RestrictInfo structure */ + foreach(orlist, indexclauses) + { + List *andlist = (List *) lfirst(orlist); + + /* Create AND subclause with RestrictInfos */ + withris = lappend(withris, make_ands_explicit(andlist)); + /* And one without */ + andlist = get_actual_clauses(andlist); + withoutris = lappend(withoutris, make_ands_explicit(andlist)); + } + return makeList1(make_restrictinfo_internal(make_orclause(withoutris), + make_orclause(withris), + is_pushed_down, + valid_everywhere)); +} + +/* + * make_restrictinfo_internal + * + * Common code for the above two entry points. + */ +static RestrictInfo * +make_restrictinfo_internal(Expr *clause, Expr *orclause, + bool is_pushed_down, bool valid_everywhere) { RestrictInfo *restrictinfo = makeNode(RestrictInfo); restrictinfo->clause = clause; + restrictinfo->orclause = orclause; restrictinfo->is_pushed_down = is_pushed_down; restrictinfo->valid_everywhere = valid_everywhere; restrictinfo->can_join = false; /* may get set below */ @@ -83,24 +167,6 @@ make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere) restrictinfo->clause_relids = pull_varnos((Node *) clause); } - /* - * If it's an OR clause, set up a modified copy with RestrictInfos - * inserted above each subclause of the top-level AND/OR structure. - */ - if (or_clause((Node *) clause)) - { - restrictinfo->orclause = make_sub_restrictinfos(clause, - is_pushed_down, - valid_everywhere); - } - else - { - /* Shouldn't be an AND clause, else flatten_andors messed up */ - Assert(!and_clause((Node *) clause)); - - restrictinfo->orclause = NULL; - } - /* * Fill in all the cacheable fields with "not yet set" markers. * None of these will be computed until/unless needed. Note in @@ -161,9 +227,10 @@ make_sub_restrictinfos(Expr *clause, bool is_pushed_down, return make_andclause(andlist); } else - return (Expr *) make_restrictinfo(clause, - is_pushed_down, - valid_everywhere); + return (Expr *) make_restrictinfo_internal(clause, + NULL, + is_pushed_down, + valid_everywhere); } /* @@ -193,9 +260,11 @@ get_actual_clauses(List *restrictinfo_list) foreach(temp, restrictinfo_list) { - RestrictInfo *clause = (RestrictInfo *) lfirst(temp); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(temp); + + Assert(IsA(rinfo, RestrictInfo)); - result = lappend(result, clause->clause); + result = lappend(result, rinfo->clause); } return result; } diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 1c3bba0ca2d10b91d42be9f9dbdcc1095d10ba48..93e8040b9b512b763db5173b8309e43381c50a72 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.152 2003/12/29 22:22:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.153 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -94,6 +94,7 @@ #include "optimizer/paths.h" #include "optimizer/plancat.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_expr.h" @@ -3896,13 +3897,13 @@ genericcostestimate(Query *root, RelOptInfo *rel, double numIndexTuples; double numIndexPages; QualCost index_qual_cost; - List *selectivityQuals = indexQuals; + List *selectivityQuals; /* * If the index is partial, AND the index predicate with the * explicitly given indexquals to produce a more accurate idea of the - * index selectivity. This may produce redundant clauses. We can get - * rid of exact duplicates by using set_union(). We expect that most + * index selectivity. This may produce redundant clauses. We get rid + * of exact duplicates in the code below. We expect that most * cases of partial redundancy (such as "x < 4" from the qual and * "x < 5" from the predicate) will be recognized and handled correctly * by clauselist_selectivity(). This assumption is somewhat fragile, @@ -3913,10 +3914,25 @@ genericcostestimate(Query *root, RelOptInfo *rel, * necessarily a bad thing. But it'd be nice to do better someday. * * Note that index->indpred and indexQuals are both in implicit-AND form, - * so ANDing them together just takes merging the lists. + * so ANDing them together just takes merging the lists. However, + * eliminating duplicates is a bit trickier because indexQuals contains + * RestrictInfo nodes and the indpred does not. It is okay to pass a + * mixed list to clauselist_selectivity, but we have to work a bit to + * generate a list without logical duplicates. (We could just set_union + * indpred and strippedQuals, but then we'd not get caching of per-qual + * selectivity estimates.) */ if (index->indpred != NIL) - selectivityQuals = set_union(index->indpred, indexQuals); + { + List *strippedQuals; + List *predExtraQuals; + + strippedQuals = get_actual_clauses(indexQuals); + predExtraQuals = set_difference(index->indpred, strippedQuals); + selectivityQuals = nconc(predExtraQuals, indexQuals); + } + else + selectivityQuals = indexQuals; /* Estimate the fraction of main-table tuples that will be visited */ *indexSelectivity = clauselist_selectivity(root, selectivityQuals, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 166f1242b0eca6125d258d54907601058449cf18..460679416f1e3c626ed45e776dfcf3b1f8edb036 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.92 2004/01/05 18:04:39 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.93 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -344,23 +344,24 @@ typedef struct Path * * 'indexinfo' is a list of IndexOptInfo nodes, one per scan to be performed. * - * 'indexqual' is a list of index qualifications, also one per scan. - * Each entry in 'indexqual' is a sublist of qualification expressions with - * implicit AND semantics across the sublist items. Only expressions that - * are usable as indexquals (as determined by indxpath.c) may appear here. - * NOTE that the semantics of the top-level list in 'indexqual' is OR + * 'indexclauses' is a list of index qualifications, also one per scan. + * Each entry in 'indexclauses' is a sublist of qualification clauses to be + * used for that scan, with implicit AND semantics across the sublist items. + * NOTE that the semantics of the top-level list in 'indexclauses' is OR * combination, while the sublists are implicitly AND combinations! - * Also note that indexquals lists do not contain RestrictInfo nodes, - * just bare clause expressions. - * - * 'indexjoinclauses' is NIL for an ordinary indexpath (one that does not - * use any join clauses in the index conditions). For an innerjoin indexpath, - * it has the same structure as 'indexqual', but references the RestrictInfo - * nodes from which the indexqual was built, rather than the bare clause - * expressions. (Note: there isn't necessarily a one-to-one correspondence - * between RestrictInfos and expressions, because of expansion of special - * indexable operators.) We need this so that we can eliminate redundant - * join clauses when plans are built. + * + * 'indexquals' has the same structure as 'indexclauses', but it contains + * the actual indexqual conditions that can be used with the index(es). + * In simple cases this is identical to 'indexclauses', but when special + * indexable operators appear in 'indexclauses', they are replaced by the + * derived indexscannable conditions in 'indexquals'. + * + * Both 'indexclauses' and 'indexquals' are lists of sublists of RestrictInfo + * nodes. (Before 7.5, we kept bare operator expressions in these lists, but + * storing RestrictInfos is more efficient since selectivities can be cached.) + * + * 'isjoininner' is TRUE if the path is a nestloop inner scan (that is, + * some of the index conditions are join rather than restriction clauses). * * 'indexscandir' is one of: * ForwardScanDirection: forward scan of an ordered index @@ -381,8 +382,9 @@ typedef struct IndexPath { Path path; List *indexinfo; - List *indexqual; - List *indexjoinclauses; + List *indexclauses; + List *indexquals; + bool isjoininner; ScanDirection indexscandir; double rows; /* estimated number of result tuples */ } IndexPath; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index e1a46bcb3f843ec01025a209fde6f4be861953de..40f7206fac380bed33e94114ded9ff30b0a5d91f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.72 2004/01/05 05:07:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.73 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,6 +44,8 @@ extern List *group_clauses_by_indexkey_for_or(RelOptInfo *rel, extern List *expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups); extern void check_partial_indexes(Query *root, RelOptInfo *rel); +extern List *flatten_clausegroups_list(List *clausegroups); +extern Expr *make_expr_from_indexclauses(List *indexclauses); /* * orindxpath.c diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 022bfa253a39e74db6309d7f691ecf43b013395e..dfb6e71e3195119a6ee576f9453a31794b21881e 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.22 2004/01/05 05:07:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.23 2004/01/05 23:39:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,6 +18,9 @@ extern RestrictInfo *make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere); +extern List *make_restrictinfo_from_indexclauses(List *indexclauses, + bool is_pushed_down, + bool valid_everywhere); extern bool restriction_is_or_clause(RestrictInfo *restrictinfo); extern List *get_actual_clauses(List *restrictinfo_list); extern void get_actual_join_clauses(List *restrictinfo_list,