diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index b486ef391e..3403269863 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3460,4 +3460,18 @@ supportfn(internal) returns internal This can be done by a support function that implements the SupportRequestRows request type. + + + For target functions that return boolean, it may be possible to + convert a function call appearing in WHERE into an indexable operator + clause or clauses. The converted clauses might be exactly equivalent + to the function's condition, or they could be somewhat weaker (that is, + they might accept some values that the function condition does not). + In the latter case the index condition is said to + be lossy; it can still be used to scan an index, + but the function call will have to be executed for each row returned by + the index to see if it really passes the WHERE condition or not. + To create such conditions, the support function must implement + the SupportRequestIndexCondition request type. + diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index c03777ed60..2d5a09b1b4 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -20,23 +20,19 @@ #include "access/stratnum.h" #include "access/sysattr.h" #include "catalog/pg_am.h" -#include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" +#include "nodes/supportnodes.h" #include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" -#include "utils/builtins.h" -#include "utils/bytea.h" #include "utils/lsyscache.h" -#include "utils/pg_locale.h" #include "utils/selfuncs.h" @@ -136,7 +132,7 @@ static double adjust_rowcount_for_semijoins(PlannerInfo *root, Index outer_relid, double rowcount); static double approximate_joinrel_size(PlannerInfo *root, Relids relids); -static void match_restriction_clauses_to_index(RelOptInfo *rel, +static void match_restriction_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, IndexClauseSet *clauseset); static void match_join_clauses_to_index(PlannerInfo *root, @@ -146,22 +142,45 @@ static void match_join_clauses_to_index(PlannerInfo *root, static void match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, IndexClauseSet *clauseset); -static void match_clauses_to_index(IndexOptInfo *index, +static void match_clauses_to_index(PlannerInfo *root, List *clauses, + IndexOptInfo *index, IndexClauseSet *clauseset); -static void match_clause_to_index(IndexOptInfo *index, +static void match_clause_to_index(PlannerInfo *root, RestrictInfo *rinfo, + IndexOptInfo *index, IndexClauseSet *clauseset); -static bool match_clause_to_indexcol(IndexOptInfo *index, +static IndexClause *match_clause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, int indexcol, - RestrictInfo *rinfo); -static bool is_indexable_operator(Oid expr_op, Oid opfamily, - bool indexkey_on_left); -static bool match_rowcompare_to_indexcol(IndexOptInfo *index, + IndexOptInfo *index); +static IndexClause *match_boolean_index_clause(RestrictInfo *rinfo, + int indexcol, IndexOptInfo *index); +static IndexClause *match_opclause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index); +static IndexClause *match_funcclause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, int indexcol, - Oid opfamily, - Oid idxcollation, - RowCompareExpr *clause); + IndexOptInfo *index); +static IndexClause *get_index_clause_from_support(PlannerInfo *root, + RestrictInfo *rinfo, + Oid funcid, + int indexarg, + int indexcol, + IndexOptInfo *index); +static IndexClause *match_saopclause_to_indexcol(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index); +static IndexClause *match_rowcompare_to_indexcol(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index); +static IndexClause *expand_indexqual_rowcompare(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index, + Oid expr_op, + bool var_on_left); static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, List **orderby_clauses_p, List **clause_columns_p); @@ -170,30 +189,6 @@ static Expr *match_clause_to_ordering_op(IndexOptInfo *index, static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *ec, EquivalenceMember *em, void *arg); -static bool match_boolean_index_clause(Node *clause, int indexcol, - IndexOptInfo *index); -static bool match_special_index_operator(Expr *clause, - Oid opfamily, Oid idxcollation, - bool indexkey_on_left); -static IndexClause *expand_indexqual_conditions(IndexOptInfo *index, - int indexcol, - RestrictInfo *rinfo); -static Expr *expand_boolean_index_clause(Node *clause, int indexcol, - IndexOptInfo *index); -static List *expand_indexqual_opclause(RestrictInfo *rinfo, - Oid opfamily, Oid idxcollation, - bool *lossy); -static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo, - IndexOptInfo *index, - int indexcol, - List **indexcolnos, - bool *lossy); -static List *prefix_quals(Node *leftop, Oid opfamily, Oid collation, - Const *prefix, Pattern_Prefix_Status pstatus); -static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, - Datum rightop); -static Datum string_to_datum(const char *str, Oid datatype); -static Const *string_to_const(const char *str, Oid datatype); /* @@ -272,7 +267,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * Identify the restriction clauses that can match the index. */ MemSet(&rclauseset, 0, sizeof(rclauseset)); - match_restriction_clauses_to_index(rel, index, &rclauseset); + match_restriction_clauses_to_index(root, index, &rclauseset); /* * Build index paths from the restriction clauses. These will be @@ -1224,7 +1219,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, * Identify the restriction clauses that can match the index. */ MemSet(&clauseset, 0, sizeof(clauseset)); - match_clauses_to_index(index, clauses, &clauseset); + match_clauses_to_index(root, clauses, index, &clauseset); /* * If no matches so far, and the index predicate isn't useful, we @@ -1236,7 +1231,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, /* * Add "other" restriction clauses to the clauseset. */ - match_clauses_to_index(index, other_clauses, &clauseset); + match_clauses_to_index(root, other_clauses, index, &clauseset); /* * Construct paths if possible. @@ -2148,11 +2143,12 @@ approximate_joinrel_size(PlannerInfo *root, Relids relids) * Matching clauses are added to *clauseset. */ static void -match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index, +match_restriction_clauses_to_index(PlannerInfo *root, + IndexOptInfo *index, IndexClauseSet *clauseset) { /* We can ignore clauses that are implied by the index predicate */ - match_clauses_to_index(index, index->indrestrictinfo, clauseset); + match_clauses_to_index(root, index->indrestrictinfo, index, clauseset); } /* @@ -2182,7 +2178,7 @@ match_join_clauses_to_index(PlannerInfo *root, if (restriction_is_or_clause(rinfo)) *joinorclauses = lappend(*joinorclauses, rinfo); else - match_clause_to_index(index, rinfo, clauseset); + match_clause_to_index(root, rinfo, index, clauseset); } } @@ -2220,7 +2216,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, * since for non-btree indexes the EC's equality operators might not * be in the index opclass (cf ec_member_matches_indexcol). */ - match_clauses_to_index(index, clauses, clauseset); + match_clauses_to_index(root, clauses, index, clauseset); } } @@ -2230,8 +2226,9 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, * Matching clauses are added to *clauseset. */ static void -match_clauses_to_index(IndexOptInfo *index, +match_clauses_to_index(PlannerInfo *root, List *clauses, + IndexOptInfo *index, IndexClauseSet *clauseset) { ListCell *lc; @@ -2240,7 +2237,7 @@ match_clauses_to_index(IndexOptInfo *index, { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); - match_clause_to_index(index, rinfo, clauseset); + match_clause_to_index(root, rinfo, index, clauseset); } } @@ -2262,8 +2259,9 @@ match_clauses_to_index(IndexOptInfo *index, * same clause multiple times with different index columns. */ static void -match_clause_to_index(IndexOptInfo *index, +match_clause_to_index(PlannerInfo *root, RestrictInfo *rinfo, + IndexOptInfo *index, IndexClauseSet *clauseset) { int indexcol; @@ -2287,6 +2285,7 @@ match_clause_to_index(IndexOptInfo *index, /* OK, check each index key column for a match */ for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++) { + IndexClause *iclause; ListCell *lc; /* Ignore duplicates */ @@ -2298,17 +2297,14 @@ match_clause_to_index(IndexOptInfo *index, return; } - /* - * XXX this should be changed so that we generate an IndexClause - * immediately upon matching, to avoid repeated work. To-do soon. - */ - if (match_clause_to_indexcol(index, - indexcol, - rinfo)) + /* OK, try to match the clause to the index column */ + iclause = match_clause_to_indexcol(root, + rinfo, + indexcol, + index); + if (iclause) { - IndexClause *iclause; - - iclause = expand_indexqual_conditions(index, indexcol, rinfo); + /* Success, so record it */ clauseset->indexclauses[indexcol] = lappend(clauseset->indexclauses[indexcol], iclause); clauseset->nonempty = true; @@ -2319,16 +2315,15 @@ match_clause_to_index(IndexOptInfo *index, /* * match_clause_to_indexcol() - * Determines whether a restriction clause matches a column of an index. + * Determine whether a restriction clause matches a column of an index, + * and if so, build an IndexClause node describing the details. * - * To match an index normally, the clause: + * To match an index normally, an operator clause: * * (1) must be in the form (indexkey op const) or (const op indexkey); * and - * (2) must contain an operator which is in the same family as the index - * operator for this column, or is a "special" operator as recognized - * by match_special_index_operator(); - * and + * (2) must contain an operator which is in the index's operator family + * for this column; and * (3) must match the collation of the index, if collation is relevant. * * Our definition of "const" is exceedingly liberal: we allow anything that @@ -2346,8 +2341,8 @@ match_clause_to_index(IndexOptInfo *index, * Presently, the executor can only deal with indexquals that have the * indexkey on the left, so we can only use clauses that have the indexkey * on the right if we can commute the clause to put the key on the left. - * We do not actually do the commuting here, but we check whether a - * suitable commutator operator is available. + * We handle that by generating an IndexClause with the correctly-commuted + * opclause as a derived indexqual. * * If the index has a collation, the clause must have the same collation. * For collation-less indexes, we assume it doesn't matter; this is @@ -2357,12 +2352,7 @@ match_clause_to_index(IndexOptInfo *index, * embodied in the macro IndexCollMatchesExprColl.) * * It is also possible to match RowCompareExpr clauses to indexes (but - * currently, only btree indexes handle this). In this routine we will - * report a match if the first column of the row comparison matches the - * target index column. This is sufficient to guarantee that some index - * condition can be constructed from the RowCompareExpr --- whether the - * remaining columns match the index too is considered in - * expand_indexqual_rowcompare(). + * currently, only btree indexes handle this). * * It is also possible to match ScalarArrayOpExpr clauses to indexes, when * the clause is of the form "indexkey op ANY (arrayconst)". @@ -2370,82 +2360,71 @@ match_clause_to_index(IndexOptInfo *index, * For boolean indexes, it is also possible to match the clause directly * to the indexkey; or perhaps the clause is (NOT indexkey). * - * 'index' is the index of interest. - * 'indexcol' is a column number of 'index' (counting from 0). + * And, last but not least, some operators and functions can be processed + * to derive (typically lossy) indexquals from a clause that isn't in + * itself indexable. If we see that any operand of an OpExpr or FuncExpr + * matches the index key, and the function has a planner support function + * attached to it, we'll invoke the support function to see if such an + * indexqual can be built. + * * 'rinfo' is the clause to be tested (as a RestrictInfo node). + * 'indexcol' is a column number of 'index' (counting from 0). + * 'index' is the index of interest. * - * Returns true if the clause can be used with this index key. + * Returns an IndexClause if the clause can be used with this index key, + * or NULL if not. * - * NOTE: returns false if clause is an OR or AND clause; it is the + * NOTE: returns NULL if clause is an OR or AND clause; it is the * responsibility of higher-level routines to cope with those. */ -static bool -match_clause_to_indexcol(IndexOptInfo *index, +static IndexClause * +match_clause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, int indexcol, - RestrictInfo *rinfo) + IndexOptInfo *index) { + IndexClause *iclause; Expr *clause = rinfo->clause; - Index index_relid = index->rel->relid; Oid opfamily; - Oid idxcollation; - Node *leftop, - *rightop; - Relids left_relids; - Relids right_relids; - Oid expr_op; - Oid expr_coll; - bool plain_op; Assert(indexcol < index->nkeycolumns); - opfamily = index->opfamily[indexcol]; - idxcollation = index->indexcollations[indexcol]; + /* + * Historically this code has coped with NULL clauses. That's probably + * not possible anymore, but we might as well continue to cope. + */ + if (clause == NULL) + return NULL; /* First check for boolean-index cases. */ + opfamily = index->opfamily[indexcol]; if (IsBooleanOpfamily(opfamily)) { - if (match_boolean_index_clause((Node *) clause, indexcol, index)) - return true; + iclause = match_boolean_index_clause(rinfo, indexcol, index); + if (iclause) + return iclause; } /* - * Clause must be a binary opclause, or possibly a ScalarArrayOpExpr - * (which is always binary, by definition). Or it could be a - * RowCompareExpr, which we pass off to match_rowcompare_to_indexcol(). - * Or, if the index supports it, we can handle IS NULL/NOT NULL clauses. + * Clause must be an opclause, funcclause, ScalarArrayOpExpr, or + * RowCompareExpr. Or, if the index supports it, we can handle IS + * NULL/NOT NULL clauses. */ - if (is_opclause(clause)) + if (IsA(clause, OpExpr)) { - leftop = get_leftop(clause); - rightop = get_rightop(clause); - if (!leftop || !rightop) - return false; - left_relids = rinfo->left_relids; - right_relids = rinfo->right_relids; - expr_op = ((OpExpr *) clause)->opno; - expr_coll = ((OpExpr *) clause)->inputcollid; - plain_op = true; + return match_opclause_to_indexcol(root, rinfo, indexcol, index); } - else if (clause && IsA(clause, ScalarArrayOpExpr)) + else if (IsA(clause, FuncExpr)) { - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; - - /* We only accept ANY clauses, not ALL */ - if (!saop->useOr) - return false; - leftop = (Node *) linitial(saop->args); - rightop = (Node *) lsecond(saop->args); - left_relids = NULL; /* not actually needed */ - right_relids = pull_varnos(rightop); - expr_op = saop->opno; - expr_coll = saop->inputcollid; - plain_op = false; + return match_funcclause_to_indexcol(root, rinfo, indexcol, index); } - else if (clause && IsA(clause, RowCompareExpr)) + else if (IsA(clause, ScalarArrayOpExpr)) { - return match_rowcompare_to_indexcol(index, indexcol, - opfamily, idxcollation, - (RowCompareExpr *) clause); + return match_saopclause_to_indexcol(rinfo, indexcol, index); + } + else if (IsA(clause, RowCompareExpr)) + { + return match_rowcompare_to_indexcol(rinfo, indexcol, index); } else if (index->amsearchnulls && IsA(clause, NullTest)) { @@ -2453,101 +2432,441 @@ match_clause_to_indexcol(IndexOptInfo *index, if (!nt->argisrow && match_index_to_operand((Node *) nt->arg, indexcol, index)) - return true; - return false; + { + iclause = makeNode(IndexClause); + iclause->rinfo = rinfo; + iclause->indexquals = NIL; + iclause->lossy = false; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + return iclause; + } } - else - return false; + + return NULL; +} + +/* + * match_boolean_index_clause + * Recognize restriction clauses that can be matched to a boolean index. + * + * The idea here is that, for an index on a boolean column that supports the + * BooleanEqualOperator, we can transform a plain reference to the indexkey + * into "indexkey = true", or "NOT indexkey" into "indexkey = false", etc, + * so as to make the expression indexable using the index's "=" operator. + * Since Postgres 8.1, we must do this because constant simplification does + * the reverse transformation; without this code there'd be no way to use + * such an index at all. + * + * This should be called only when IsBooleanOpfamily() recognizes the + * index's operator family. We check to see if the clause matches the + * index's key, and if so, build a suitable IndexClause. + */ +static IndexClause * +match_boolean_index_clause(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index) +{ + Node *clause = (Node *) rinfo->clause; + Expr *op = NULL; + + /* Direct match? */ + if (match_index_to_operand(clause, indexcol, index)) + { + /* convert to indexkey = TRUE */ + op = make_opclause(BooleanEqualOperator, BOOLOID, false, + (Expr *) clause, + (Expr *) makeBoolConst(true, false), + InvalidOid, InvalidOid); + } + /* NOT clause? */ + else if (is_notclause(clause)) + { + Node *arg = (Node *) get_notclausearg((Expr *) clause); + + if (match_index_to_operand(arg, indexcol, index)) + { + /* convert to indexkey = FALSE */ + op = make_opclause(BooleanEqualOperator, BOOLOID, false, + (Expr *) arg, + (Expr *) makeBoolConst(false, false), + InvalidOid, InvalidOid); + } + } + + /* + * Since we only consider clauses at top level of WHERE, we can convert + * indexkey IS TRUE and indexkey IS FALSE to index searches as well. The + * different meaning for NULL isn't important. + */ + else if (clause && IsA(clause, BooleanTest)) + { + BooleanTest *btest = (BooleanTest *) clause; + Node *arg = (Node *) btest->arg; + + if (btest->booltesttype == IS_TRUE && + match_index_to_operand(arg, indexcol, index)) + { + /* convert to indexkey = TRUE */ + op = make_opclause(BooleanEqualOperator, BOOLOID, false, + (Expr *) arg, + (Expr *) makeBoolConst(true, false), + InvalidOid, InvalidOid); + } + else if (btest->booltesttype == IS_FALSE && + match_index_to_operand(arg, indexcol, index)) + { + /* convert to indexkey = FALSE */ + op = make_opclause(BooleanEqualOperator, BOOLOID, false, + (Expr *) arg, + (Expr *) makeBoolConst(false, false), + InvalidOid, InvalidOid); + } + } + + /* + * If we successfully made an operator clause from the given qual, we must + * wrap it in an IndexClause. It's not lossy. + */ + if (op) + { + IndexClause *iclause = makeNode(IndexClause); + + iclause->rinfo = rinfo; + iclause->indexquals = list_make1(make_simple_restrictinfo(op)); + iclause->lossy = false; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + return iclause; + } + + return NULL; +} + +/* + * match_opclause_to_indexcol() + * Handles the OpExpr case for match_clause_to_indexcol(), + * which see for comments. + */ +static IndexClause * +match_opclause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index) +{ + IndexClause *iclause; + OpExpr *clause = (OpExpr *) rinfo->clause; + Node *leftop, + *rightop; + Oid expr_op; + Oid expr_coll; + Index index_relid; + Oid opfamily; + Oid idxcollation; + + /* + * Only binary operators need apply. (In theory, a planner support + * function could do something with a unary operator, but it seems + * unlikely to be worth the cycles to check.) + */ + if (list_length(clause->args) != 2) + return NULL; + + leftop = (Node *) linitial(clause->args); + rightop = (Node *) lsecond(clause->args); + expr_op = clause->opno; + expr_coll = clause->inputcollid; + + index_relid = index->rel->relid; + opfamily = index->opfamily[indexcol]; + idxcollation = index->indexcollations[indexcol]; /* * Check for clauses of the form: (indexkey operator constant) or - * (constant operator indexkey). See above notes about const-ness. + * (constant operator indexkey). See match_clause_to_indexcol's notes + * about const-ness. + * + * Note that we don't ask the support function about clauses that don't + * have one of these forms. Again, in principle it might be possible to + * do something, but it seems unlikely to be worth the cycles to check. + */ + if (match_index_to_operand(leftop, indexcol, index) && + !bms_is_member(index_relid, rinfo->right_relids) && + !contain_volatile_functions(rightop)) + { + if (IndexCollMatchesExprColl(idxcollation, expr_coll) && + op_in_opfamily(expr_op, opfamily)) + { + iclause = makeNode(IndexClause); + iclause->rinfo = rinfo; + iclause->indexquals = NIL; + iclause->lossy = false; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + return iclause; + } + + /* + * If we didn't find a member of the index's opfamily, try the support + * function for the operator's underlying function. + */ + set_opfuncid(clause); /* make sure we have opfuncid */ + return get_index_clause_from_support(root, + rinfo, + clause->opfuncid, + 0, /* indexarg on left */ + indexcol, + index); + } + + if (match_index_to_operand(rightop, indexcol, index) && + !bms_is_member(index_relid, rinfo->left_relids) && + !contain_volatile_functions(leftop)) + { + if (IndexCollMatchesExprColl(idxcollation, expr_coll)) + { + Oid comm_op = get_commutator(expr_op); + + if (OidIsValid(comm_op) && + op_in_opfamily(comm_op, opfamily)) + { + RestrictInfo *commrinfo; + + /* Build a commuted OpExpr and RestrictInfo */ + commrinfo = commute_restrictinfo(rinfo, comm_op); + + /* Make an IndexClause showing that as a derived qual */ + iclause = makeNode(IndexClause); + iclause->rinfo = rinfo; + iclause->indexquals = list_make1(commrinfo); + iclause->lossy = false; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + return iclause; + } + } + + /* + * If we didn't find a member of the index's opfamily, try the support + * function for the operator's underlying function. + */ + set_opfuncid(clause); /* make sure we have opfuncid */ + return get_index_clause_from_support(root, + rinfo, + clause->opfuncid, + 1, /* indexarg on right */ + indexcol, + index); + } + + return NULL; +} + +/* + * match_funcclause_to_indexcol() + * Handles the FuncExpr case for match_clause_to_indexcol(), + * which see for comments. + */ +static IndexClause * +match_funcclause_to_indexcol(PlannerInfo *root, + RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index) +{ + FuncExpr *clause = (FuncExpr *) rinfo->clause; + int indexarg; + ListCell *lc; + + /* + * We have no built-in intelligence about function clauses, but if there's + * a planner support function, it might be able to do something. But, to + * cut down on wasted planning cycles, only call the support function if + * at least one argument matches the target index column. + * + * Note that we don't insist on the other arguments being pseudoconstants; + * the support function has to check that. This is to allow cases where + * only some of the other arguments need to be included in the indexqual. + */ + indexarg = 0; + foreach(lc, clause->args) + { + Node *op = (Node *) lfirst(lc); + + if (match_index_to_operand(op, indexcol, index)) + { + return get_index_clause_from_support(root, + rinfo, + clause->funcid, + indexarg, + indexcol, + index); + } + + indexarg++; + } + + return NULL; +} + +/* + * get_index_clause_from_support() + * If the function has a planner support function, try to construct + * an IndexClause using indexquals created by the support function. + */ +static IndexClause * +get_index_clause_from_support(PlannerInfo *root, + RestrictInfo *rinfo, + Oid funcid, + int indexarg, + int indexcol, + IndexOptInfo *index) +{ + Oid prosupport = get_func_support(funcid); + SupportRequestIndexCondition req; + List *sresult; + + if (!OidIsValid(prosupport)) + return NULL; + + req.type = T_SupportRequestIndexCondition; + req.root = root; + req.funcid = funcid; + req.node = (Node *) rinfo->clause; + req.indexarg = indexarg; + req.index = index; + req.indexcol = indexcol; + req.opfamily = index->opfamily[indexcol]; + req.indexcollation = index->indexcollations[indexcol]; + + req.lossy = true; /* default assumption */ + + sresult = (List *) + DatumGetPointer(OidFunctionCall1(prosupport, + PointerGetDatum(&req))); + + if (sresult != NIL) + { + IndexClause *iclause = makeNode(IndexClause); + List *indexquals = NIL; + ListCell *lc; + + /* + * The support function API says it should just give back bare + * clauses, so here we must wrap each one in a RestrictInfo. + */ + foreach(lc, sresult) + { + Expr *clause = (Expr *) lfirst(lc); + + indexquals = lappend(indexquals, make_simple_restrictinfo(clause)); + } + + iclause->rinfo = rinfo; + iclause->indexquals = indexquals; + iclause->lossy = req.lossy; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + + return iclause; + } + + return NULL; +} + +/* + * match_saopclause_to_indexcol() + * Handles the ScalarArrayOpExpr case for match_clause_to_indexcol(), + * which see for comments. + */ +static IndexClause * +match_saopclause_to_indexcol(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index) +{ + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause; + Node *leftop, + *rightop; + Relids right_relids; + Oid expr_op; + Oid expr_coll; + Index index_relid; + Oid opfamily; + Oid idxcollation; + + /* We only accept ANY clauses, not ALL */ + if (!saop->useOr) + return NULL; + leftop = (Node *) linitial(saop->args); + rightop = (Node *) lsecond(saop->args); + right_relids = pull_varnos(rightop); + expr_op = saop->opno; + expr_coll = saop->inputcollid; + + index_relid = index->rel->relid; + opfamily = index->opfamily[indexcol]; + idxcollation = index->indexcollations[indexcol]; + + /* + * We must have indexkey on the left and a pseudo-constant array argument. */ if (match_index_to_operand(leftop, indexcol, index) && !bms_is_member(index_relid, right_relids) && !contain_volatile_functions(rightop)) { if (IndexCollMatchesExprColl(idxcollation, expr_coll) && - is_indexable_operator(expr_op, opfamily, true)) - return true; + op_in_opfamily(expr_op, opfamily)) + { + IndexClause *iclause = makeNode(IndexClause); + + iclause->rinfo = rinfo; + iclause->indexquals = NIL; + iclause->lossy = false; + iclause->indexcol = indexcol; + iclause->indexcols = NIL; + return iclause; + } /* - * If we didn't find a member of the index's opfamily, see whether it - * is a "special" indexable operator. + * We do not currently ask support functions about ScalarArrayOpExprs, + * though in principle we could. */ - if (plain_op && - match_special_index_operator(clause, opfamily, - idxcollation, true)) - return true; - return false; } - if (plain_op && - match_index_to_operand(rightop, indexcol, index) && - !bms_is_member(index_relid, left_relids) && - !contain_volatile_functions(leftop)) - { - if (IndexCollMatchesExprColl(idxcollation, expr_coll) && - is_indexable_operator(expr_op, opfamily, false)) - return true; - - /* - * If we didn't find a member of the index's opfamily, see whether it - * is a "special" indexable operator. - */ - if (match_special_index_operator(clause, opfamily, - idxcollation, false)) - return true; - return false; - } - - return false; -} - -/* - * is_indexable_operator - * Does the operator match the specified index opfamily? - * - * If the indexkey is on the right, what we actually want to know - * is whether the operator has a commutator operator that matches - * the opfamily. - */ -static bool -is_indexable_operator(Oid expr_op, Oid opfamily, bool indexkey_on_left) -{ - /* Get the commuted operator if necessary */ - if (!indexkey_on_left) - { - expr_op = get_commutator(expr_op); - if (expr_op == InvalidOid) - return false; - } - - /* OK if the (commuted) operator is a member of the index's opfamily */ - return op_in_opfamily(expr_op, opfamily); + return NULL; } /* * match_rowcompare_to_indexcol() * Handles the RowCompareExpr case for match_clause_to_indexcol(), * which see for comments. + * + * In this routine we check whether the first column of the row comparison + * matches the target index column. This is sufficient to guarantee that some + * index condition can be constructed from the RowCompareExpr --- the rest + * is handled by expand_indexqual_rowcompare(). */ -static bool -match_rowcompare_to_indexcol(IndexOptInfo *index, +static IndexClause * +match_rowcompare_to_indexcol(RestrictInfo *rinfo, int indexcol, - Oid opfamily, - Oid idxcollation, - RowCompareExpr *clause) + IndexOptInfo *index) { - Index index_relid = index->rel->relid; + RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause; + Index index_relid; + Oid opfamily; + Oid idxcollation; Node *leftop, *rightop; + bool var_on_left; Oid expr_op; Oid expr_coll; /* Forget it if we're not dealing with a btree index */ if (index->relam != BTREE_AM_OID) - return false; + return NULL; + + index_relid = index->rel->relid; + opfamily = index->opfamily[indexcol]; + idxcollation = index->indexcollations[indexcol]; /* * We could do the matching on the basis of insisting that the opfamily @@ -2566,16 +2885,17 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, /* Collations must match, if relevant */ if (!IndexCollMatchesExprColl(idxcollation, expr_coll)) - return false; + return NULL; /* - * These syntactic tests are the same as in match_clause_to_indexcol() + * These syntactic tests are the same as in match_opclause_to_indexcol() */ if (match_index_to_operand(leftop, indexcol, index) && !bms_is_member(index_relid, pull_varnos(rightop)) && !contain_volatile_functions(rightop)) { /* OK, indexkey is on left */ + var_on_left = true; } else if (match_index_to_operand(rightop, indexcol, index) && !bms_is_member(index_relid, pull_varnos(leftop)) && @@ -2584,10 +2904,11 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, /* indexkey is on right, so commute the operator */ expr_op = get_commutator(expr_op); if (expr_op == InvalidOid) - return false; + return NULL; + var_on_left = false; } else - return false; + return NULL; /* We're good if the operator is the right type of opfamily member */ switch (get_op_opfamily_strategy(expr_op, opfamily)) @@ -2596,10 +2917,250 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, case BTLessEqualStrategyNumber: case BTGreaterEqualStrategyNumber: case BTGreaterStrategyNumber: - return true; + return expand_indexqual_rowcompare(rinfo, + indexcol, + index, + expr_op, + var_on_left); } - return false; + return NULL; +} + +/* + * expand_indexqual_rowcompare --- expand a single indexqual condition + * that is a RowCompareExpr + * + * It's already known that the first column of the row comparison matches + * the specified column of the index. We can use additional columns of the + * row comparison as index qualifications, so long as they match the index + * in the "same direction", ie, the indexkeys are all on the same side of the + * clause and the operators are all the same-type members of the opfamilies. + * + * If all the columns of the RowCompareExpr match in this way, we just use it + * as-is, except for possibly commuting it to put the indexkeys on the left. + * + * Otherwise, we build a shortened RowCompareExpr (if more than one + * column matches) or a simple OpExpr (if the first-column match is all + * there is). In these cases the modified clause is always "<=" or ">=" + * even when the original was "<" or ">" --- this is necessary to match all + * the rows that could match the original. (We are building a lossy version + * of the row comparison when we do this, so we set lossy = true.) + * + * Note: this is really just the last half of match_rowcompare_to_indexcol, + * but we split it out for comprehensibility. + */ +static IndexClause * +expand_indexqual_rowcompare(RestrictInfo *rinfo, + int indexcol, + IndexOptInfo *index, + Oid expr_op, + bool var_on_left) +{ + IndexClause *iclause = makeNode(IndexClause); + RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause; + int op_strategy; + Oid op_lefttype; + Oid op_righttype; + int matching_cols; + List *expr_ops; + List *opfamilies; + List *lefttypes; + List *righttypes; + List *new_ops; + List *var_args; + List *non_var_args; + ListCell *vargs_cell; + ListCell *nargs_cell; + ListCell *opnos_cell; + ListCell *collids_cell; + + iclause->rinfo = rinfo; + iclause->indexcol = indexcol; + + if (var_on_left) + { + var_args = clause->largs; + non_var_args = clause->rargs; + } + else + { + var_args = clause->rargs; + non_var_args = clause->largs; + } + + get_op_opfamily_properties(expr_op, index->opfamily[indexcol], false, + &op_strategy, + &op_lefttype, + &op_righttype); + + /* Initialize returned list of which index columns are used */ + iclause->indexcols = list_make1_int(indexcol); + + /* Build lists of ops, opfamilies and operator datatypes in case needed */ + expr_ops = list_make1_oid(expr_op); + opfamilies = list_make1_oid(index->opfamily[indexcol]); + lefttypes = list_make1_oid(op_lefttype); + righttypes = list_make1_oid(op_righttype); + + /* + * See how many of the remaining columns match some index column in the + * same way. As in match_clause_to_indexcol(), the "other" side of any + * potential index condition is OK as long as it doesn't use Vars from the + * indexed relation. + */ + matching_cols = 1; + vargs_cell = lnext(list_head(var_args)); + nargs_cell = lnext(list_head(non_var_args)); + opnos_cell = lnext(list_head(clause->opnos)); + collids_cell = lnext(list_head(clause->inputcollids)); + + while (vargs_cell != NULL) + { + Node *varop = (Node *) lfirst(vargs_cell); + Node *constop = (Node *) lfirst(nargs_cell); + int i; + + expr_op = lfirst_oid(opnos_cell); + if (!var_on_left) + { + /* indexkey is on right, so commute the operator */ + expr_op = get_commutator(expr_op); + if (expr_op == InvalidOid) + break; /* operator is not usable */ + } + if (bms_is_member(index->rel->relid, pull_varnos(constop))) + break; /* no good, Var on wrong side */ + if (contain_volatile_functions(constop)) + break; /* no good, volatile comparison value */ + + /* + * The Var side can match any key column of the index. + */ + for (i = 0; i < index->nkeycolumns; i++) + { + if (match_index_to_operand(varop, i, index) && + get_op_opfamily_strategy(expr_op, + index->opfamily[i]) == op_strategy && + IndexCollMatchesExprColl(index->indexcollations[i], + lfirst_oid(collids_cell))) + break; + } + if (i >= index->nkeycolumns) + break; /* no match found */ + + /* Add column number to returned list */ + iclause->indexcols = lappend_int(iclause->indexcols, i); + + /* Add operator info to lists */ + get_op_opfamily_properties(expr_op, index->opfamily[i], false, + &op_strategy, + &op_lefttype, + &op_righttype); + expr_ops = lappend_oid(expr_ops, expr_op); + opfamilies = lappend_oid(opfamilies, index->opfamily[i]); + lefttypes = lappend_oid(lefttypes, op_lefttype); + righttypes = lappend_oid(righttypes, op_righttype); + + /* This column matches, keep scanning */ + matching_cols++; + vargs_cell = lnext(vargs_cell); + nargs_cell = lnext(nargs_cell); + opnos_cell = lnext(opnos_cell); + collids_cell = lnext(collids_cell); + } + + /* Result is non-lossy if all columns are usable as index quals */ + iclause->lossy = (matching_cols != list_length(clause->opnos)); + + /* + * We can use rinfo->clause as-is if we have var on left and it's all + * usable as index quals. + */ + if (var_on_left && !iclause->lossy) + iclause->indexquals = NIL; + else + { + /* + * We have to generate a modified rowcompare (possibly just one + * OpExpr). The painful part of this is changing < to <= or > to >=, + * so deal with that first. + */ + if (!iclause->lossy) + { + /* very easy, just use the commuted operators */ + new_ops = expr_ops; + } + else if (op_strategy == BTLessEqualStrategyNumber || + op_strategy == BTGreaterEqualStrategyNumber) + { + /* easy, just use the same (possibly commuted) operators */ + new_ops = list_truncate(expr_ops, matching_cols); + } + else + { + ListCell *opfamilies_cell; + ListCell *lefttypes_cell; + ListCell *righttypes_cell; + + if (op_strategy == BTLessStrategyNumber) + op_strategy = BTLessEqualStrategyNumber; + else if (op_strategy == BTGreaterStrategyNumber) + op_strategy = BTGreaterEqualStrategyNumber; + else + elog(ERROR, "unexpected strategy number %d", op_strategy); + new_ops = NIL; + forthree(opfamilies_cell, opfamilies, + lefttypes_cell, lefttypes, + righttypes_cell, righttypes) + { + Oid opfam = lfirst_oid(opfamilies_cell); + Oid lefttype = lfirst_oid(lefttypes_cell); + Oid righttype = lfirst_oid(righttypes_cell); + + expr_op = get_opfamily_member(opfam, lefttype, righttype, + op_strategy); + if (!OidIsValid(expr_op)) /* should not happen */ + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", + op_strategy, lefttype, righttype, opfam); + new_ops = lappend_oid(new_ops, expr_op); + } + } + + /* If we have more than one matching col, create a subset rowcompare */ + if (matching_cols > 1) + { + RowCompareExpr *rc = makeNode(RowCompareExpr); + + rc->rctype = (RowCompareType) op_strategy; + rc->opnos = new_ops; + rc->opfamilies = list_truncate(list_copy(clause->opfamilies), + matching_cols); + rc->inputcollids = list_truncate(list_copy(clause->inputcollids), + matching_cols); + rc->largs = list_truncate(copyObject(var_args), + matching_cols); + rc->rargs = list_truncate(copyObject(non_var_args), + matching_cols); + iclause->indexquals = list_make1(make_simple_restrictinfo((Expr *) rc)); + } + else + { + Expr *op; + + /* We don't report an index column list in this case */ + iclause->indexcols = NIL; + + op = make_opclause(linitial_oid(new_ops), BOOLOID, false, + copyObject(linitial(var_args)), + copyObject(linitial(non_var_args)), + InvalidOid, + linitial_oid(clause->inputcollids)); + iclause->indexquals = list_make1(make_simple_restrictinfo(op)); + } + } + + return iclause; } @@ -3233,7 +3794,7 @@ indexcol_is_bool_constant_for_query(IndexOptInfo *index, int indexcol) continue; /* See if we can match the clause's expression to the index column */ - if (match_boolean_index_clause((Node *) rinfo->clause, indexcol, index)) + if (match_boolean_index_clause(rinfo, indexcol, index)) return true; } @@ -3324,1056 +3885,33 @@ match_index_to_operand(Node *operand, return false; } -/**************************************************************************** - * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS ---- - ****************************************************************************/ - /* - * These routines handle special optimization of operators that can be - * used with index scans even though they are not known to the executor's - * indexscan machinery. The key idea is that these operators allow us - * to derive approximate indexscan qual clauses, such that any tuples - * that pass the operator clause itself must also satisfy the simpler - * indexscan condition(s). Then we can use the indexscan machinery - * to avoid scanning as much of the table as we'd otherwise have to, - * while applying the original operator as a qpqual condition to ensure - * we deliver only the tuples we want. (In essence, we're using a regular - * index as if it were a lossy index.) + * is_pseudo_constant_for_index() + * Test whether the given expression can be used as an indexscan + * comparison value. * - * An example of what we're doing is - * textfield LIKE 'abc%' - * from which we can generate the indexscanable conditions - * textfield >= 'abc' AND textfield < 'abd' - * which allow efficient scanning of an index on textfield. - * (In reality, character set and collation issues make the transformation - * from LIKE to indexscan limits rather harder than one might think ... - * but that's the basic idea.) + * An indexscan comparison value must not contain any volatile functions, + * and it can't contain any Vars of the index's own table. Vars of + * other tables are okay, though; in that case we'd be producing an + * indexqual usable in a parameterized indexscan. This is, therefore, + * a weaker condition than is_pseudo_constant_clause(). * - * Another thing that we do with this machinery is to provide special - * smarts for "boolean" indexes (that is, indexes on boolean columns - * that support boolean equality). We can transform a plain reference - * to the indexkey into "indexkey = true", or "NOT indexkey" into - * "indexkey = false", so as to make the expression indexable using the - * regular index operators. (As of Postgres 8.1, we must do this here - * because constant simplification does the reverse transformation; - * without this code there'd be no way to use such an index at all.) + * This function is exported for use by planner support functions, + * which will have available the IndexOptInfo, but not any RestrictInfo + * infrastructure. It is making the same test made by functions above + * such as match_opclause_to_indexcol(), but those rely where possible + * on RestrictInfo information about variable membership. * - * Three routines are provided here: - * - * match_special_index_operator() is just an auxiliary function for - * match_clause_to_indexcol(); after the latter fails to recognize a - * restriction opclause's operator as a member of an index's opfamily, - * it asks match_special_index_operator() whether the clause should be - * considered an indexqual anyway. - * - * match_boolean_index_clause() similarly detects clauses that can be - * converted into boolean equality operators. - * - * expand_indexqual_conditions() converts a RestrictInfo node - * into an IndexClause, which contains clauses - * that the executor can actually handle. For operators that are members of - * the index's opfamily this transformation is a no-op, but clauses recognized - * by match_special_index_operator() or match_boolean_index_clause() must be - * converted into one or more "regular" indexqual conditions. + * expr: the nodetree to be checked + * index: the index of interest */ - -/* - * match_boolean_index_clause - * Recognize restriction clauses that can be matched to a boolean index. - * - * This should be called only when IsBooleanOpfamily() recognizes the - * index's operator family. We check to see if the clause matches the - * index's key. - */ -static bool -match_boolean_index_clause(Node *clause, - int indexcol, - IndexOptInfo *index) +bool +is_pseudo_constant_for_index(Node *expr, IndexOptInfo *index) { - /* Direct match? */ - if (match_index_to_operand(clause, indexcol, index)) - return true; - /* NOT clause? */ - if (is_notclause(clause)) - { - if (match_index_to_operand((Node *) get_notclausearg((Expr *) clause), - indexcol, index)) - return true; - } - - /* - * Since we only consider clauses at top level of WHERE, we can convert - * indexkey IS TRUE and indexkey IS FALSE to index searches as well. The - * different meaning for NULL isn't important. - */ - else if (clause && IsA(clause, BooleanTest)) - { - BooleanTest *btest = (BooleanTest *) clause; - - if (btest->booltesttype == IS_TRUE || - btest->booltesttype == IS_FALSE) - if (match_index_to_operand((Node *) btest->arg, - indexcol, index)) - return true; - } - return false; -} - -/* - * match_special_index_operator - * Recognize restriction clauses that can be used to generate - * additional indexscanable qualifications. - * - * The given clause is already known to be a binary opclause having - * the form (indexkey OP pseudoconst) or (pseudoconst OP indexkey), - * but the OP proved not to be one of the index's opfamily operators. - * Return 'true' if we can do something with it anyway. - */ -static bool -match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation, - bool indexkey_on_left) -{ - bool isIndexable = false; - Node *rightop; - Oid expr_op; - Oid expr_coll; - Const *patt; - Const *prefix = NULL; - Pattern_Prefix_Status pstatus = Pattern_Prefix_None; - - /* - * Currently, all known special operators require the indexkey on the - * left, but this test could be pushed into the switch statement if some - * are added that do not... - */ - if (!indexkey_on_left) - return false; - - /* we know these will succeed */ - rightop = get_rightop(clause); - expr_op = ((OpExpr *) clause)->opno; - expr_coll = ((OpExpr *) clause)->inputcollid; - - /* again, required for all current special ops: */ - if (!IsA(rightop, Const) || - ((Const *) rightop)->constisnull) - return false; - patt = (Const *) rightop; - - switch (expr_op) - { - case OID_TEXT_LIKE_OP: - case OID_BPCHAR_LIKE_OP: - case OID_NAME_LIKE_OP: - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, - &prefix, NULL); - isIndexable = (pstatus != Pattern_Prefix_None); - break; - - case OID_BYTEA_LIKE_OP: - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, - &prefix, NULL); - isIndexable = (pstatus != Pattern_Prefix_None); - break; - - case OID_TEXT_ICLIKE_OP: - case OID_BPCHAR_ICLIKE_OP: - case OID_NAME_ICLIKE_OP: - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, expr_coll, - &prefix, NULL); - isIndexable = (pstatus != Pattern_Prefix_None); - break; - - case OID_TEXT_REGEXEQ_OP: - case OID_BPCHAR_REGEXEQ_OP: - case OID_NAME_REGEXEQ_OP: - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, expr_coll, - &prefix, NULL); - isIndexable = (pstatus != Pattern_Prefix_None); - break; - - case OID_TEXT_ICREGEXEQ_OP: - case OID_BPCHAR_ICREGEXEQ_OP: - case OID_NAME_ICREGEXEQ_OP: - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, expr_coll, - &prefix, NULL); - isIndexable = (pstatus != Pattern_Prefix_None); - break; - - case OID_INET_SUB_OP: - case OID_INET_SUBEQ_OP: - isIndexable = true; - break; - } - - if (prefix) - { - pfree(DatumGetPointer(prefix->constvalue)); - pfree(prefix); - } - - /* done if the expression doesn't look indexable */ - if (!isIndexable) - return false; - - /* - * Must also check that index's opfamily supports the operators we will - * want to apply. (A hash index, for example, will not support ">=".) - * Currently, only btree and spgist support the operators we need. - * - * Note: actually, in the Pattern_Prefix_Exact case, we only need "=" so a - * hash index would work. Currently it doesn't seem worth checking for - * that, however. - * - * We insist on the opfamily being the specific one we expect, else we'd - * do the wrong thing if someone were to make a reverse-sort opfamily with - * the same operators. - * - * The non-pattern opclasses will not sort the way we need in most non-C - * locales. We can use such an index anyway for an exact match (simple - * equality), but not for prefix-match cases. Note that here we are - * looking at the index's collation, not the expression's collation -- - * this test is *not* dependent on the LIKE/regex operator's collation. - */ - switch (expr_op) - { - case OID_TEXT_LIKE_OP: - case OID_TEXT_ICLIKE_OP: - case OID_TEXT_REGEXEQ_OP: - case OID_TEXT_ICREGEXEQ_OP: - case OID_NAME_LIKE_OP: - case OID_NAME_ICLIKE_OP: - case OID_NAME_REGEXEQ_OP: - case OID_NAME_ICREGEXEQ_OP: - isIndexable = - (opfamily == TEXT_PATTERN_BTREE_FAM_OID) || - (opfamily == TEXT_SPGIST_FAM_OID) || - (opfamily == TEXT_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || - lc_collate_is_c(idxcollation))); - break; - - case OID_BPCHAR_LIKE_OP: - case OID_BPCHAR_ICLIKE_OP: - case OID_BPCHAR_REGEXEQ_OP: - case OID_BPCHAR_ICREGEXEQ_OP: - isIndexable = - (opfamily == BPCHAR_PATTERN_BTREE_FAM_OID) || - (opfamily == BPCHAR_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || - lc_collate_is_c(idxcollation))); - break; - - case OID_BYTEA_LIKE_OP: - isIndexable = (opfamily == BYTEA_BTREE_FAM_OID); - break; - - case OID_INET_SUB_OP: - case OID_INET_SUBEQ_OP: - isIndexable = (opfamily == NETWORK_BTREE_FAM_OID); - break; - } - - return isIndexable; -} - -/* - * expand_indexqual_conditions - * Given a RestrictInfo node, create an IndexClause. - * - * Standard qual clauses (those in the index's opfamily) are passed through - * unchanged. Boolean clauses and "special" index operators are expanded - * into clauses that the indexscan machinery will know what to do with. - * RowCompare clauses are simplified if necessary to create a clause that is - * fully checkable by the index. - */ -static IndexClause * -expand_indexqual_conditions(IndexOptInfo *index, - int indexcol, - RestrictInfo *rinfo) -{ - IndexClause *iclause = makeNode(IndexClause); - List *indexquals = NIL; - - iclause->rinfo = rinfo; - iclause->lossy = false; /* might get changed below */ - iclause->indexcol = indexcol; - iclause->indexcols = NIL; /* might get changed below */ - - { - Expr *clause = rinfo->clause; - Oid curFamily; - Oid curCollation; - - Assert(indexcol < index->nkeycolumns); - - curFamily = index->opfamily[indexcol]; - curCollation = index->indexcollations[indexcol]; - - /* First check for boolean cases */ - if (IsBooleanOpfamily(curFamily)) - { - Expr *boolqual; - - boolqual = expand_boolean_index_clause((Node *) clause, - indexcol, - index); - if (boolqual) - { - iclause->indexquals = - list_make1(make_simple_restrictinfo(boolqual)); - return iclause; - } - } - - /* - * Else it must be an opclause (usual case), ScalarArrayOp, - * RowCompare, or NullTest - */ - if (is_opclause(clause)) - { - /* - * Check to see if the indexkey is on the right; if so, commute - * the clause. The indexkey should be the side that refers to - * (only) the base relation. - */ - if (!bms_equal(rinfo->left_relids, index->rel->relids)) - { - Oid opno = ((OpExpr *) clause)->opno; - RestrictInfo *newrinfo; - - newrinfo = commute_restrictinfo(rinfo, - get_commutator(opno)); - - /* - * For now, assume it couldn't be any case that requires - * expansion. (This is OK for the current capabilities of - * expand_indexqual_opclause, but we'll need to remove the - * restriction when we open this up for extensions.) - */ - indexquals = list_make1(newrinfo); - } - else - indexquals = expand_indexqual_opclause(rinfo, - curFamily, - curCollation, - &iclause->lossy); - } - else if (IsA(clause, ScalarArrayOpExpr)) - { - /* no extra work at this time */ - } - else if (IsA(clause, RowCompareExpr)) - { - RestrictInfo *newrinfo; - - newrinfo = expand_indexqual_rowcompare(rinfo, - index, - indexcol, - &iclause->indexcols, - &iclause->lossy); - if (newrinfo != rinfo) - { - /* We need to report a derived expression */ - indexquals = list_make1(newrinfo); - } - } - else if (IsA(clause, NullTest)) - { - Assert(index->amsearchnulls); - } - else - elog(ERROR, "unsupported indexqual type: %d", - (int) nodeTag(clause)); - } - - iclause->indexquals = indexquals; - return iclause; -} - -/* - * expand_boolean_index_clause - * Convert a clause recognized by match_boolean_index_clause into - * a boolean equality operator clause. - * - * Returns NULL if the clause isn't a boolean index qual. - */ -static Expr * -expand_boolean_index_clause(Node *clause, - int indexcol, - IndexOptInfo *index) -{ - /* Direct match? */ - if (match_index_to_operand(clause, indexcol, index)) - { - /* convert to indexkey = TRUE */ - return make_opclause(BooleanEqualOperator, BOOLOID, false, - (Expr *) clause, - (Expr *) makeBoolConst(true, false), - InvalidOid, InvalidOid); - } - /* NOT clause? */ - if (is_notclause(clause)) - { - Node *arg = (Node *) get_notclausearg((Expr *) clause); - - /* It must have matched the indexkey */ - Assert(match_index_to_operand(arg, indexcol, index)); - /* convert to indexkey = FALSE */ - return make_opclause(BooleanEqualOperator, BOOLOID, false, - (Expr *) arg, - (Expr *) makeBoolConst(false, false), - InvalidOid, InvalidOid); - } - if (clause && IsA(clause, BooleanTest)) - { - BooleanTest *btest = (BooleanTest *) clause; - Node *arg = (Node *) btest->arg; - - /* It must have matched the indexkey */ - Assert(match_index_to_operand(arg, indexcol, index)); - if (btest->booltesttype == IS_TRUE) - { - /* convert to indexkey = TRUE */ - return make_opclause(BooleanEqualOperator, BOOLOID, false, - (Expr *) arg, - (Expr *) makeBoolConst(true, false), - InvalidOid, InvalidOid); - } - if (btest->booltesttype == IS_FALSE) - { - /* convert to indexkey = FALSE */ - return make_opclause(BooleanEqualOperator, BOOLOID, false, - (Expr *) arg, - (Expr *) makeBoolConst(false, false), - InvalidOid, InvalidOid); - } - /* Oops */ - Assert(false); - } - - return NULL; -} - -/* - * expand_indexqual_opclause --- expand a single indexqual condition - * that is an operator clause - * - * The input is a single RestrictInfo, the output a list of RestrictInfos, - * or NIL if the RestrictInfo's clause can be used as-is. - * - * In the base case this is just "return NIL", but we have to be prepared to - * expand special cases that were accepted by match_special_index_operator(). - */ -static List * -expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation, - bool *lossy) -{ - Expr *clause = rinfo->clause; - - /* we know these will succeed */ - Node *leftop = get_leftop(clause); - Node *rightop = get_rightop(clause); - Oid expr_op = ((OpExpr *) clause)->opno; - Oid expr_coll = ((OpExpr *) clause)->inputcollid; - Const *patt = (Const *) rightop; - Const *prefix = NULL; - Pattern_Prefix_Status pstatus; - - /* - * LIKE and regex operators are not members of any btree index opfamily, - * but they can be members of opfamilies for more exotic index types such - * as GIN. Therefore, we should only do expansion if the operator is - * actually not in the opfamily. But checking that requires a syscache - * lookup, so it's best to first see if the operator is one we are - * interested in. - */ - switch (expr_op) - { - case OID_TEXT_LIKE_OP: - case OID_BPCHAR_LIKE_OP: - case OID_NAME_LIKE_OP: - case OID_BYTEA_LIKE_OP: - if (!op_in_opfamily(expr_op, opfamily)) - { - *lossy = true; - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, expr_coll, - &prefix, NULL); - return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); - } - break; - - case OID_TEXT_ICLIKE_OP: - case OID_BPCHAR_ICLIKE_OP: - case OID_NAME_ICLIKE_OP: - if (!op_in_opfamily(expr_op, opfamily)) - { - *lossy = true; - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, expr_coll, - &prefix, NULL); - return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); - } - break; - - case OID_TEXT_REGEXEQ_OP: - case OID_BPCHAR_REGEXEQ_OP: - case OID_NAME_REGEXEQ_OP: - if (!op_in_opfamily(expr_op, opfamily)) - { - *lossy = true; - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, expr_coll, - &prefix, NULL); - return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); - } - break; - - case OID_TEXT_ICREGEXEQ_OP: - case OID_BPCHAR_ICREGEXEQ_OP: - case OID_NAME_ICREGEXEQ_OP: - if (!op_in_opfamily(expr_op, opfamily)) - { - *lossy = true; - /* the right-hand const is type text for all of these */ - pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, expr_coll, - &prefix, NULL); - return prefix_quals(leftop, opfamily, idxcollation, prefix, pstatus); - } - break; - - case OID_INET_SUB_OP: - case OID_INET_SUBEQ_OP: - if (!op_in_opfamily(expr_op, opfamily)) - { - *lossy = true; - return network_prefix_quals(leftop, expr_op, opfamily, - patt->constvalue); - } - break; - } - - /* Default case: the clause can be used as-is. */ - *lossy = false; - return NIL; -} - -/* - * expand_indexqual_rowcompare --- expand a single indexqual condition - * that is a RowCompareExpr - * - * It's already known that the first column of the row comparison matches - * the specified column of the index. We can use additional columns of the - * row comparison as index qualifications, so long as they match the index - * in the "same direction", ie, the indexkeys are all on the same side of the - * clause and the operators are all the same-type members of the opfamilies. - * - * If all the columns of the RowCompareExpr match in this way, we just use it - * as-is, except for possibly commuting it to put the indexkeys on the left. - * - * Otherwise, we build a shortened RowCompareExpr (if more than one - * column matches) or a simple OpExpr (if the first-column match is all - * there is). In these cases the modified clause is always "<=" or ">=" - * even when the original was "<" or ">" --- this is necessary to match all - * the rows that could match the original. (We are building a lossy version - * of the row comparison when we do this, so we set *lossy = true.) - * - * *indexcolnos receives an integer list of the index column numbers (zero - * based) used in the resulting expression. We have to pass that back - * because createplan.c will need it. - */ -static RestrictInfo * -expand_indexqual_rowcompare(RestrictInfo *rinfo, - IndexOptInfo *index, - int indexcol, - List **indexcolnos, - bool *lossy) -{ - RowCompareExpr *clause = castNode(RowCompareExpr, rinfo->clause); - bool var_on_left; - int op_strategy; - Oid op_lefttype; - Oid op_righttype; - int matching_cols; - Oid expr_op; - List *expr_ops; - List *opfamilies; - List *lefttypes; - List *righttypes; - List *new_ops; - List *var_args; - List *non_var_args; - ListCell *vargs_cell; - ListCell *nargs_cell; - ListCell *opnos_cell; - ListCell *collids_cell; - - /* We have to figure out (again) how the first col matches */ - var_on_left = match_index_to_operand((Node *) linitial(clause->largs), - indexcol, index); - Assert(var_on_left || - match_index_to_operand((Node *) linitial(clause->rargs), - indexcol, index)); - - if (var_on_left) - { - var_args = clause->largs; - non_var_args = clause->rargs; - } - else - { - var_args = clause->rargs; - non_var_args = clause->largs; - } - - expr_op = linitial_oid(clause->opnos); - if (!var_on_left) - expr_op = get_commutator(expr_op); - get_op_opfamily_properties(expr_op, index->opfamily[indexcol], false, - &op_strategy, - &op_lefttype, - &op_righttype); - - /* Initialize returned list of which index columns are used */ - *indexcolnos = list_make1_int(indexcol); - - /* Build lists of ops, opfamilies and operator datatypes in case needed */ - expr_ops = list_make1_oid(expr_op); - opfamilies = list_make1_oid(index->opfamily[indexcol]); - lefttypes = list_make1_oid(op_lefttype); - righttypes = list_make1_oid(op_righttype); - - /* - * See how many of the remaining columns match some index column in the - * same way. As in match_clause_to_indexcol(), the "other" side of any - * potential index condition is OK as long as it doesn't use Vars from the - * indexed relation. - */ - matching_cols = 1; - vargs_cell = lnext(list_head(var_args)); - nargs_cell = lnext(list_head(non_var_args)); - opnos_cell = lnext(list_head(clause->opnos)); - collids_cell = lnext(list_head(clause->inputcollids)); - - while (vargs_cell != NULL) - { - Node *varop = (Node *) lfirst(vargs_cell); - Node *constop = (Node *) lfirst(nargs_cell); - int i; - - expr_op = lfirst_oid(opnos_cell); - if (!var_on_left) - { - /* indexkey is on right, so commute the operator */ - expr_op = get_commutator(expr_op); - if (expr_op == InvalidOid) - break; /* operator is not usable */ - } - if (bms_is_member(index->rel->relid, pull_varnos(constop))) - break; /* no good, Var on wrong side */ - if (contain_volatile_functions(constop)) - break; /* no good, volatile comparison value */ - - /* - * The Var side can match any key column of the index. - */ - for (i = 0; i < index->nkeycolumns; i++) - { - if (match_index_to_operand(varop, i, index) && - get_op_opfamily_strategy(expr_op, - index->opfamily[i]) == op_strategy && - IndexCollMatchesExprColl(index->indexcollations[i], - lfirst_oid(collids_cell))) - - break; - } - if (i >= index->nkeycolumns) - break; /* no match found */ - - /* Add column number to returned list */ - *indexcolnos = lappend_int(*indexcolnos, i); - - /* Add operator info to lists */ - get_op_opfamily_properties(expr_op, index->opfamily[i], false, - &op_strategy, - &op_lefttype, - &op_righttype); - expr_ops = lappend_oid(expr_ops, expr_op); - opfamilies = lappend_oid(opfamilies, index->opfamily[i]); - lefttypes = lappend_oid(lefttypes, op_lefttype); - righttypes = lappend_oid(righttypes, op_righttype); - - /* This column matches, keep scanning */ - matching_cols++; - vargs_cell = lnext(vargs_cell); - nargs_cell = lnext(nargs_cell); - opnos_cell = lnext(opnos_cell); - collids_cell = lnext(collids_cell); - } - - /* Result is non-lossy if all columns are usable as index quals */ - *lossy = (matching_cols != list_length(clause->opnos)); - - /* - * Return clause as-is if we have var on left and it's all usable as index - * quals - */ - if (var_on_left && !*lossy) - return rinfo; - - /* - * We have to generate a modified rowcompare (possibly just one OpExpr). - * The painful part of this is changing < to <= or > to >=, so deal with - * that first. - */ - if (!*lossy) - { - /* very easy, just use the commuted operators */ - new_ops = expr_ops; - } - else if (op_strategy == BTLessEqualStrategyNumber || - op_strategy == BTGreaterEqualStrategyNumber) - { - /* easy, just use the same (possibly commuted) operators */ - new_ops = list_truncate(expr_ops, matching_cols); - } - else - { - ListCell *opfamilies_cell; - ListCell *lefttypes_cell; - ListCell *righttypes_cell; - - if (op_strategy == BTLessStrategyNumber) - op_strategy = BTLessEqualStrategyNumber; - else if (op_strategy == BTGreaterStrategyNumber) - op_strategy = BTGreaterEqualStrategyNumber; - else - elog(ERROR, "unexpected strategy number %d", op_strategy); - new_ops = NIL; - forthree(opfamilies_cell, opfamilies, - lefttypes_cell, lefttypes, - righttypes_cell, righttypes) - { - Oid opfam = lfirst_oid(opfamilies_cell); - Oid lefttype = lfirst_oid(lefttypes_cell); - Oid righttype = lfirst_oid(righttypes_cell); - - expr_op = get_opfamily_member(opfam, lefttype, righttype, - op_strategy); - if (!OidIsValid(expr_op)) /* should not happen */ - elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", - op_strategy, lefttype, righttype, opfam); - new_ops = lappend_oid(new_ops, expr_op); - } - } - - /* If we have more than one matching col, create a subset rowcompare */ - if (matching_cols > 1) - { - RowCompareExpr *rc = makeNode(RowCompareExpr); - - rc->rctype = (RowCompareType) op_strategy; - rc->opnos = new_ops; - rc->opfamilies = list_truncate(list_copy(clause->opfamilies), - matching_cols); - rc->inputcollids = list_truncate(list_copy(clause->inputcollids), - matching_cols); - rc->largs = list_truncate(copyObject(var_args), - matching_cols); - rc->rargs = list_truncate(copyObject(non_var_args), - matching_cols); - return make_simple_restrictinfo((Expr *) rc); - } - else - { - Expr *op; - - /* We don't report an index column list in this case */ - *indexcolnos = NIL; - - op = make_opclause(linitial_oid(new_ops), BOOLOID, false, - copyObject(linitial(var_args)), - copyObject(linitial(non_var_args)), - InvalidOid, - linitial_oid(clause->inputcollids)); - return make_simple_restrictinfo(op); - } -} - -/* - * Given a fixed prefix that all the "leftop" values must have, - * generate suitable indexqual condition(s). opfamily is the index - * operator family; we use it to deduce the appropriate comparison - * operators and operand datatypes. collation is the input collation to use. - */ -static List * -prefix_quals(Node *leftop, Oid opfamily, Oid collation, - Const *prefix_const, Pattern_Prefix_Status pstatus) -{ - List *result; - Oid ldatatype = exprType(leftop); - Oid rdatatype; - Oid oproid; - Expr *expr; - FmgrInfo ltproc; - Const *greaterstr; - - Assert(pstatus != Pattern_Prefix_None); - - switch (opfamily) - { - case TEXT_BTREE_FAM_OID: - case TEXT_PATTERN_BTREE_FAM_OID: - case TEXT_SPGIST_FAM_OID: - rdatatype = TEXTOID; - break; - - case BPCHAR_BTREE_FAM_OID: - case BPCHAR_PATTERN_BTREE_FAM_OID: - rdatatype = BPCHAROID; - break; - - case BYTEA_BTREE_FAM_OID: - rdatatype = BYTEAOID; - break; - - default: - /* shouldn't get here */ - elog(ERROR, "unexpected opfamily: %u", opfamily); - return NIL; - } - - /* - * If necessary, coerce the prefix constant to the right type. The given - * prefix constant is either text or bytea type. - */ - if (prefix_const->consttype != rdatatype) - { - char *prefix; - - switch (prefix_const->consttype) - { - case TEXTOID: - prefix = TextDatumGetCString(prefix_const->constvalue); - break; - case BYTEAOID: - prefix = DatumGetCString(DirectFunctionCall1(byteaout, - prefix_const->constvalue)); - break; - default: - elog(ERROR, "unexpected const type: %u", - prefix_const->consttype); - return NIL; - } - prefix_const = string_to_const(prefix, rdatatype); - pfree(prefix); - } - - /* - * If we found an exact-match pattern, generate an "=" indexqual. - */ - if (pstatus == Pattern_Prefix_Exact) - { - oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, - BTEqualStrategyNumber); - if (oproid == InvalidOid) - elog(ERROR, "no = operator for opfamily %u", opfamily); - expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) prefix_const, - InvalidOid, collation); - result = list_make1(make_simple_restrictinfo(expr)); - return result; - } - - /* - * Otherwise, we have a nonempty required prefix of the values. - * - * We can always say "x >= prefix". - */ - oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, - BTGreaterEqualStrategyNumber); - if (oproid == InvalidOid) - elog(ERROR, "no >= operator for opfamily %u", opfamily); - expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) prefix_const, - InvalidOid, collation); - result = list_make1(make_simple_restrictinfo(expr)); - - /*------- - * If we can create a string larger than the prefix, we can say - * "x < greaterstr". NB: we rely on make_greater_string() to generate - * a guaranteed-greater string, not just a probably-greater string. - * In general this is only guaranteed in C locale, so we'd better be - * using a C-locale index collation. - *------- - */ - oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, - BTLessStrategyNumber); - if (oproid == InvalidOid) - elog(ERROR, "no < operator for opfamily %u", opfamily); - fmgr_info(get_opcode(oproid), <proc); - greaterstr = make_greater_string(prefix_const, <proc, collation); - if (greaterstr) - { - expr = make_opclause(oproid, BOOLOID, false, - (Expr *) leftop, (Expr *) greaterstr, - InvalidOid, collation); - result = lappend(result, make_simple_restrictinfo(expr)); - } - - return result; -} - -/* - * Given a leftop and a rightop, and an inet-family sup/sub operator, - * generate suitable indexqual condition(s). expr_op is the original - * operator, and opfamily is the index opfamily. - */ -static List * -network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, Datum rightop) -{ - bool is_eq; - Oid datatype; - Oid opr1oid; - Oid opr2oid; - Datum opr1right; - Datum opr2right; - List *result; - Expr *expr; - - switch (expr_op) - { - case OID_INET_SUB_OP: - datatype = INETOID; - is_eq = false; - break; - case OID_INET_SUBEQ_OP: - datatype = INETOID; - is_eq = true; - break; - default: - elog(ERROR, "unexpected operator: %u", expr_op); - return NIL; - } - - /* - * create clause "key >= network_scan_first( rightop )", or ">" if the - * operator disallows equality. - */ - if (is_eq) - { - opr1oid = get_opfamily_member(opfamily, datatype, datatype, - BTGreaterEqualStrategyNumber); - if (opr1oid == InvalidOid) - elog(ERROR, "no >= operator for opfamily %u", opfamily); - } - else - { - opr1oid = get_opfamily_member(opfamily, datatype, datatype, - BTGreaterStrategyNumber); - if (opr1oid == InvalidOid) - elog(ERROR, "no > operator for opfamily %u", opfamily); - } - - opr1right = network_scan_first(rightop); - - expr = make_opclause(opr1oid, BOOLOID, false, - (Expr *) leftop, - (Expr *) makeConst(datatype, -1, - InvalidOid, /* not collatable */ - -1, opr1right, - false, false), - InvalidOid, InvalidOid); - result = list_make1(make_simple_restrictinfo(expr)); - - /* create clause "key <= network_scan_last( rightop )" */ - - opr2oid = get_opfamily_member(opfamily, datatype, datatype, - BTLessEqualStrategyNumber); - if (opr2oid == InvalidOid) - elog(ERROR, "no <= operator for opfamily %u", opfamily); - - opr2right = network_scan_last(rightop); - - expr = make_opclause(opr2oid, BOOLOID, false, - (Expr *) leftop, - (Expr *) makeConst(datatype, -1, - InvalidOid, /* not collatable */ - -1, opr2right, - false, false), - InvalidOid, InvalidOid); - result = lappend(result, make_simple_restrictinfo(expr)); - - return result; -} - -/* - * Handy subroutines for match_special_index_operator() and friends. - */ - -/* - * Generate a Datum of the appropriate type from a C string. - * Note that all of the supported types are pass-by-ref, so the - * returned value should be pfree'd if no longer needed. - */ -static Datum -string_to_datum(const char *str, Oid datatype) -{ - /* - * We cheat a little by assuming that CStringGetTextDatum() will do for - * bpchar and varchar constants too... - */ - if (datatype == NAMEOID) - return DirectFunctionCall1(namein, CStringGetDatum(str)); - else if (datatype == BYTEAOID) - return DirectFunctionCall1(byteain, CStringGetDatum(str)); - else - return CStringGetTextDatum(str); -} - -/* - * Generate a Const node of the appropriate type from a C string. - */ -static Const * -string_to_const(const char *str, Oid datatype) -{ - Datum conval = string_to_datum(str, datatype); - Oid collation; - int constlen; - - /* - * We only need to support a few datatypes here, so hard-wire properties - * instead of incurring the expense of catalog lookups. - */ - switch (datatype) - { - case TEXTOID: - case VARCHAROID: - case BPCHAROID: - collation = DEFAULT_COLLATION_OID; - constlen = -1; - break; - - case NAMEOID: - collation = C_COLLATION_OID; - constlen = NAMEDATALEN; - break; - - case BYTEAOID: - collation = InvalidOid; - constlen = -1; - break; - - default: - elog(ERROR, "unexpected datatype in string_to_const: %u", - datatype); - return NULL; - } - - return makeConst(datatype, -1, collation, constlen, - conval, false, false); + /* pull_varnos is cheaper than volatility check, so do that first */ + if (bms_is_member(index->rel->relid, pull_varnos(expr))) + return false; /* no good, contains Var of table */ + if (contain_volatile_functions(expr)) + return false; /* no good, volatile comparison value */ + return true; } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 86e4753a5b..3737166e8f 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2067,7 +2067,8 @@ is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK) * Params and outer-level Vars, not to mention functions whose results * may vary from one statement to the next. However, the expr's value * will be constant over any one scan of the current query, so it can be - * used as, eg, an indexscan key. + * used as, eg, an indexscan key. (Actually, the condition for indexscan + * keys is weaker than this; see is_pseudo_constant_for_index().) * * CAUTION: this function omits to test for one very important class of * not-constant expressions, namely aggregates (Aggrefs). In current usage diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 20eead1798..82d10af752 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,7 +17,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ float.o format_type.o formatting.o genfile.o \ geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \ int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \ - jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o name.o \ + jsonfuncs.o like.o like_support.o lockfuncs.o \ + mac.o mac8.o misc.o name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \ diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c new file mode 100644 index 0000000000..b001dde5fc --- /dev/null +++ b/src/backend/utils/adt/like_support.c @@ -0,0 +1,313 @@ +/*------------------------------------------------------------------------- + * + * like_support.c + * Planner support functions for LIKE, regex, and related operators. + * + * These routines handle special optimization of operators that can be + * used with index scans even though they are not known to the executor's + * indexscan machinery. The key idea is that these operators allow us + * to derive approximate indexscan qual clauses, such that any tuples + * that pass the operator clause itself must also satisfy the simpler + * indexscan condition(s). Then we can use the indexscan machinery + * to avoid scanning as much of the table as we'd otherwise have to, + * while applying the original operator as a qpqual condition to ensure + * we deliver only the tuples we want. (In essence, we're using a regular + * index as if it were a lossy index.) + * + * An example of what we're doing is + * textfield LIKE 'abc%def' + * from which we can generate the indexscanable conditions + * textfield >= 'abc' AND textfield < 'abd' + * which allow efficient scanning of an index on textfield. + * (In reality, character set and collation issues make the transformation + * from LIKE to indexscan limits rather harder than one might think ... + * but that's the basic idea.) + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/like_support.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/stratnum.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/pg_locale.h" +#include "utils/selfuncs.h" + + +static Node *like_regex_support(Node *rawreq, Pattern_Type ptype); +static List *match_pattern_prefix(Node *leftop, + Node *rightop, + Pattern_Type ptype, + Oid expr_coll, + Oid opfamily, + Oid indexcollation); + + +/* + * Planner support functions for LIKE, regex, and related operators + */ +Datum +textlike_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like)); +} + +Datum +texticlike_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like_IC)); +} + +Datum +textregexeq_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex)); +} + +Datum +texticregexeq_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex_IC)); +} + +/* Common code for the above */ +static Node * +like_regex_support(Node *rawreq, Pattern_Type ptype) +{ + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestIndexCondition)) + { + /* Try to convert operator/function call to index conditions */ + SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq; + + /* + * Currently we have no "reverse" match operators with the pattern on + * the left, so we only need consider cases with the indexkey on the + * left. + */ + if (req->indexarg != 0) + return NULL; + + if (is_opclause(req->node)) + { + OpExpr *clause = (OpExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_pattern_prefix((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + ptype, + clause->inputcollid, + req->opfamily, + req->indexcollation); + } + else if (is_funcclause(req->node)) /* be paranoid */ + { + FuncExpr *clause = (FuncExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_pattern_prefix((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + ptype, + clause->inputcollid, + req->opfamily, + req->indexcollation); + } + } + + return ret; +} + +/* + * match_pattern_prefix + * Try to generate an indexqual for a LIKE or regex operator. + */ +static List * +match_pattern_prefix(Node *leftop, + Node *rightop, + Pattern_Type ptype, + Oid expr_coll, + Oid opfamily, + Oid indexcollation) +{ + List *result; + Const *patt; + Const *prefix; + Pattern_Prefix_Status pstatus; + Oid ldatatype; + Oid rdatatype; + Oid oproid; + Expr *expr; + FmgrInfo ltproc; + Const *greaterstr; + + /* + * Can't do anything with a non-constant or NULL pattern argument. + * + * Note that since we restrict ourselves to cases with a hard constant on + * the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry + * about verifying that. + */ + if (!IsA(rightop, Const) || + ((Const *) rightop)->constisnull) + return NIL; + patt = (Const *) rightop; + + /* + * Try to extract a fixed prefix from the pattern. + */ + pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, + &prefix, NULL); + + /* fail if no fixed prefix */ + if (pstatus == Pattern_Prefix_None) + return NIL; + + /* + * Must also check that index's opfamily supports the operators we will + * want to apply. (A hash index, for example, will not support ">=".) + * Currently, only btree and spgist support the operators we need. + * + * Note: actually, in the Pattern_Prefix_Exact case, we only need "=" so a + * hash index would work. Currently it doesn't seem worth checking for + * that, however. + * + * We insist on the opfamily being one of the specific ones we expect, + * else we'd do the wrong thing if someone were to make a reverse-sort + * opfamily with the same operators. + * + * The non-pattern opclasses will not sort the way we need in most non-C + * locales. We can use such an index anyway for an exact match (simple + * equality), but not for prefix-match cases. Note that here we are + * looking at the index's collation, not the expression's collation -- + * this test is *not* dependent on the LIKE/regex operator's collation. + * + * While we're at it, identify the type the comparison constant(s) should + * have, based on the opfamily. + */ + switch (opfamily) + { + case TEXT_BTREE_FAM_OID: + if (!(pstatus == Pattern_Prefix_Exact || + lc_collate_is_c(indexcollation))) + return NIL; + rdatatype = TEXTOID; + break; + + case TEXT_PATTERN_BTREE_FAM_OID: + case TEXT_SPGIST_FAM_OID: + rdatatype = TEXTOID; + break; + + case BPCHAR_BTREE_FAM_OID: + if (!(pstatus == Pattern_Prefix_Exact || + lc_collate_is_c(indexcollation))) + return NIL; + rdatatype = BPCHAROID; + break; + + case BPCHAR_PATTERN_BTREE_FAM_OID: + rdatatype = BPCHAROID; + break; + + case BYTEA_BTREE_FAM_OID: + rdatatype = BYTEAOID; + break; + + default: + return NIL; + } + + /* OK, prepare to create the indexqual(s) */ + ldatatype = exprType(leftop); + + /* + * If necessary, coerce the prefix constant to the right type. The given + * prefix constant is either text or bytea type, therefore the only case + * where we need to do anything is when converting text to bpchar. Those + * two types are binary-compatible, so relabeling the Const node is + * sufficient. + */ + if (prefix->consttype != rdatatype) + { + Assert(prefix->consttype == TEXTOID && + rdatatype == BPCHAROID); + prefix->consttype = rdatatype; + } + + /* + * If we found an exact-match pattern, generate an "=" indexqual. + */ + if (pstatus == Pattern_Prefix_Exact) + { + oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTEqualStrategyNumber); + if (oproid == InvalidOid) + elog(ERROR, "no = operator for opfamily %u", opfamily); + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix, + InvalidOid, indexcollation); + result = list_make1(expr); + return result; + } + + /* + * Otherwise, we have a nonempty required prefix of the values. + * + * We can always say "x >= prefix". + */ + oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTGreaterEqualStrategyNumber); + if (oproid == InvalidOid) + elog(ERROR, "no >= operator for opfamily %u", opfamily); + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix, + InvalidOid, indexcollation); + result = list_make1(expr); + + /*------- + * If we can create a string larger than the prefix, we can say + * "x < greaterstr". NB: we rely on make_greater_string() to generate + * a guaranteed-greater string, not just a probably-greater string. + * In general this is only guaranteed in C locale, so we'd better be + * using a C-locale index collation. + *------- + */ + oproid = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTLessStrategyNumber); + if (oproid == InvalidOid) + elog(ERROR, "no < operator for opfamily %u", opfamily); + fmgr_info(get_opcode(oproid), <proc); + greaterstr = make_greater_string(prefix, <proc, indexcollation); + if (greaterstr) + { + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) greaterstr, + InvalidOid, indexcollation); + result = lappend(result, expr); + } + + return result; +} diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c index 5af7f4e046..7f3ca7f930 100644 --- a/src/backend/utils/adt/network.c +++ b/src/backend/utils/adt/network.c @@ -13,16 +13,31 @@ #include #include "access/hash.h" +#include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" #include "common/ip.h" #include "libpq/libpq-be.h" #include "libpq/pqformat.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/inet.h" +#include "utils/lsyscache.h" static int32 network_cmp_internal(inet *a1, inet *a2); +static List *match_network_function(Node *leftop, + Node *rightop, + int indexarg, + Oid funcid, + Oid opfamily); +static List *match_network_subset(Node *leftop, + Node *rightop, + bool is_eq, + Oid opfamily); static bool addressOK(unsigned char *a, int bits, int family); static inet *internal_inetpl(inet *ip, int64 addend); @@ -574,6 +589,198 @@ network_overlap(PG_FUNCTION_ARGS) PG_RETURN_BOOL(false); } +/* + * Planner support function for network subset/superset operators + */ +Datum +network_subset_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestIndexCondition)) + { + /* Try to convert operator/function call to index conditions */ + SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq; + + if (is_opclause(req->node)) + { + OpExpr *clause = (OpExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_network_function((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + req->indexarg, + req->funcid, + req->opfamily); + } + else if (is_funcclause(req->node)) /* be paranoid */ + { + FuncExpr *clause = (FuncExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_network_function((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + req->indexarg, + req->funcid, + req->opfamily); + } + } + + PG_RETURN_POINTER(ret); +} + +/* + * match_network_function + * Try to generate an indexqual for a network subset/superset function. + * + * This layer is just concerned with identifying the function and swapping + * the arguments if necessary. + */ +static List * +match_network_function(Node *leftop, + Node *rightop, + int indexarg, + Oid funcid, + Oid opfamily) +{ + switch (funcid) + { + case F_NETWORK_SUB: + /* indexkey must be on the left */ + if (indexarg != 0) + return NIL; + return match_network_subset(leftop, rightop, false, opfamily); + + case F_NETWORK_SUBEQ: + /* indexkey must be on the left */ + if (indexarg != 0) + return NIL; + return match_network_subset(leftop, rightop, true, opfamily); + + case F_NETWORK_SUP: + /* indexkey must be on the right */ + if (indexarg != 1) + return NIL; + return match_network_subset(rightop, leftop, false, opfamily); + + case F_NETWORK_SUPEQ: + /* indexkey must be on the right */ + if (indexarg != 1) + return NIL; + return match_network_subset(rightop, leftop, true, opfamily); + + default: + + /* + * We'd only get here if somebody attached this support function + * to an unexpected function. Maybe we should complain, but for + * now, do nothing. + */ + return NIL; + } +} + +/* + * match_network_subset + * Try to generate an indexqual for a network subset function. + */ +static List * +match_network_subset(Node *leftop, + Node *rightop, + bool is_eq, + Oid opfamily) +{ + List *result; + Datum rightopval; + Oid datatype = INETOID; + Oid opr1oid; + Oid opr2oid; + Datum opr1right; + Datum opr2right; + Expr *expr; + + /* + * Can't do anything with a non-constant or NULL comparison value. + * + * Note that since we restrict ourselves to cases with a hard constant on + * the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry + * about verifying that. + */ + if (!IsA(rightop, Const) || + ((Const *) rightop)->constisnull) + return NIL; + rightopval = ((Const *) rightop)->constvalue; + + /* + * Must check that index's opfamily supports the operators we will want to + * apply. + * + * We insist on the opfamily being the specific one we expect, else we'd + * do the wrong thing if someone were to make a reverse-sort opfamily with + * the same operators. + */ + if (opfamily != NETWORK_BTREE_FAM_OID) + return NIL; + + /* + * create clause "key >= network_scan_first( rightopval )", or ">" if the + * operator disallows equality. + * + * Note: seeing that this function supports only fixed values for opfamily + * and datatype, we could just hard-wire the operator OIDs instead of + * looking them up. But for now it seems better to be general. + */ + if (is_eq) + { + opr1oid = get_opfamily_member(opfamily, datatype, datatype, + BTGreaterEqualStrategyNumber); + if (opr1oid == InvalidOid) + elog(ERROR, "no >= operator for opfamily %u", opfamily); + } + else + { + opr1oid = get_opfamily_member(opfamily, datatype, datatype, + BTGreaterStrategyNumber); + if (opr1oid == InvalidOid) + elog(ERROR, "no > operator for opfamily %u", opfamily); + } + + opr1right = network_scan_first(rightopval); + + expr = make_opclause(opr1oid, BOOLOID, false, + (Expr *) leftop, + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr1right, + false, false), + InvalidOid, InvalidOid); + result = list_make1(expr); + + /* create clause "key <= network_scan_last( rightopval )" */ + + opr2oid = get_opfamily_member(opfamily, datatype, datatype, + BTLessEqualStrategyNumber); + if (opr2oid == InvalidOid) + elog(ERROR, "no <= operator for opfamily %u", opfamily); + + opr2right = network_scan_last(rightopval); + + expr = make_opclause(opr2oid, BOOLOID, false, + (Expr *) leftop, + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr2right, + false, false), + InvalidOid, InvalidOid); + result = lappend(result, expr); + + return result; +} + + /* * Extract data from a network datatype. */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 7a65fde762..3f4e199762 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201902111 +#define CATALOG_VERSION_NO 201902112 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 50b742c06e..24f99f7fc4 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -189,17 +189,21 @@ prosrc => 'i4tochar' }, { oid => '79', - proname => 'nameregexeq', prorettype => 'bool', proargtypes => 'name text', - prosrc => 'nameregexeq' }, + proname => 'nameregexeq', prosupport => 'textregexeq_support', + prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameregexeq' }, { oid => '1252', proname => 'nameregexne', prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameregexne' }, { oid => '1254', - proname => 'textregexeq', prorettype => 'bool', proargtypes => 'text text', - prosrc => 'textregexeq' }, + proname => 'textregexeq', prosupport => 'textregexeq_support', + prorettype => 'bool', proargtypes => 'text text', prosrc => 'textregexeq' }, { oid => '1256', proname => 'textregexne', prorettype => 'bool', proargtypes => 'text text', prosrc => 'textregexne' }, +{ oid => '1364', descr => 'planner support for textregexeq', + proname => 'textregexeq_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'textregexeq_support' }, + { oid => '1257', descr => 'length', proname => 'textlen', prorettype => 'int4', proargtypes => 'text', prosrc => 'textlen' }, @@ -1637,8 +1641,11 @@ proname => 'position', prorettype => 'int4', proargtypes => 'text text', prosrc => 'textpos' }, { oid => '850', - proname => 'textlike', prorettype => 'bool', proargtypes => 'text text', - prosrc => 'textlike' }, + proname => 'textlike', prosupport => 'textlike_support', prorettype => 'bool', + proargtypes => 'text text', prosrc => 'textlike' }, +{ oid => '1023', descr => 'planner support for textlike', + proname => 'textlike_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'textlike_support' }, { oid => '851', proname => 'textnlike', prorettype => 'bool', proargtypes => 'text text', prosrc => 'textnlike' }, @@ -1663,8 +1670,8 @@ proargtypes => 'int4 int8', prosrc => 'int48ge' }, { oid => '858', - proname => 'namelike', prorettype => 'bool', proargtypes => 'name text', - prosrc => 'namelike' }, + proname => 'namelike', prosupport => 'textlike_support', prorettype => 'bool', + proargtypes => 'name text', prosrc => 'namelike' }, { oid => '859', proname => 'namenlike', prorettype => 'bool', proargtypes => 'name text', prosrc => 'namenlike' }, @@ -2354,14 +2361,17 @@ prosrc => 'int8smaller' }, { oid => '1238', - proname => 'texticregexeq', prorettype => 'bool', proargtypes => 'text text', - prosrc => 'texticregexeq' }, + proname => 'texticregexeq', prosupport => 'texticregexeq_support', + prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticregexeq' }, +{ oid => '1024', descr => 'planner support for texticregexeq', + proname => 'texticregexeq_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'texticregexeq_support' }, { oid => '1239', proname => 'texticregexne', prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticregexne' }, { oid => '1240', - proname => 'nameicregexeq', prorettype => 'bool', proargtypes => 'name text', - prosrc => 'nameicregexeq' }, + proname => 'nameicregexeq', prosupport => 'texticregexeq_support', + prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameicregexeq' }, { oid => '1241', proname => 'nameicregexne', prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameicregexne' }, @@ -3130,14 +3140,14 @@ prosrc => 'bittypmodout' }, { oid => '1569', descr => 'matches LIKE expression', - proname => 'like', prorettype => 'bool', proargtypes => 'text text', - prosrc => 'textlike' }, + proname => 'like', prosupport => 'textlike_support', prorettype => 'bool', + proargtypes => 'text text', prosrc => 'textlike' }, { oid => '1570', descr => 'does not match LIKE expression', proname => 'notlike', prorettype => 'bool', proargtypes => 'text text', prosrc => 'textnlike' }, { oid => '1571', descr => 'matches LIKE expression', - proname => 'like', prorettype => 'bool', proargtypes => 'name text', - prosrc => 'namelike' }, + proname => 'like', prosupport => 'textlike_support', prorettype => 'bool', + proargtypes => 'name text', prosrc => 'namelike' }, { oid => '1572', descr => 'does not match LIKE expression', proname => 'notlike', prorettype => 'bool', proargtypes => 'name text', prosrc => 'namenlike' }, @@ -3301,21 +3311,24 @@ proargtypes => 'float8 interval', prosrc => 'mul_d_interval' }, { oid => '1631', - proname => 'bpcharlike', prorettype => 'bool', proargtypes => 'bpchar text', - prosrc => 'textlike' }, + proname => 'bpcharlike', prosupport => 'textlike_support', + prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textlike' }, { oid => '1632', proname => 'bpcharnlike', prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textnlike' }, { oid => '1633', - proname => 'texticlike', prorettype => 'bool', proargtypes => 'text text', - prosrc => 'texticlike' }, + proname => 'texticlike', prosupport => 'texticlike_support', + prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticlike' }, +{ oid => '1025', descr => 'planner support for texticlike', + proname => 'texticlike_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'texticlike_support' }, { oid => '1634', proname => 'texticnlike', prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticnlike' }, { oid => '1635', - proname => 'nameiclike', prorettype => 'bool', proargtypes => 'name text', - prosrc => 'nameiclike' }, + proname => 'nameiclike', prosupport => 'texticlike_support', + prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameiclike' }, { oid => '1636', proname => 'nameicnlike', prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameicnlike' }, @@ -3324,20 +3337,21 @@ prosrc => 'like_escape' }, { oid => '1656', - proname => 'bpcharicregexeq', prorettype => 'bool', - proargtypes => 'bpchar text', prosrc => 'texticregexeq' }, + proname => 'bpcharicregexeq', prosupport => 'texticregexeq_support', + prorettype => 'bool', proargtypes => 'bpchar text', + prosrc => 'texticregexeq' }, { oid => '1657', proname => 'bpcharicregexne', prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'texticregexne' }, { oid => '1658', - proname => 'bpcharregexeq', prorettype => 'bool', - proargtypes => 'bpchar text', prosrc => 'textregexeq' }, + proname => 'bpcharregexeq', prosupport => 'textregexeq_support', + prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textregexeq' }, { oid => '1659', proname => 'bpcharregexne', prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textregexne' }, { oid => '1660', - proname => 'bpchariclike', prorettype => 'bool', proargtypes => 'bpchar text', - prosrc => 'texticlike' }, + proname => 'bpchariclike', prosupport => 'texticlike_support', + prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'texticlike' }, { oid => '1661', proname => 'bpcharicnlike', prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'texticnlike' }, @@ -3878,17 +3892,21 @@ proname => 'network_cmp', proleakproof => 't', prorettype => 'int4', proargtypes => 'inet inet', prosrc => 'network_cmp' }, { oid => '927', - proname => 'network_sub', prorettype => 'bool', proargtypes => 'inet inet', - prosrc => 'network_sub' }, + proname => 'network_sub', prosupport => 'network_subset_support', + prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_sub' }, { oid => '928', - proname => 'network_subeq', prorettype => 'bool', proargtypes => 'inet inet', - prosrc => 'network_subeq' }, + proname => 'network_subeq', prosupport => 'network_subset_support', + prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_subeq' }, { oid => '929', - proname => 'network_sup', prorettype => 'bool', proargtypes => 'inet inet', - prosrc => 'network_sup' }, + proname => 'network_sup', prosupport => 'network_subset_support', + prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_sup' }, { oid => '930', - proname => 'network_supeq', prorettype => 'bool', proargtypes => 'inet inet', - prosrc => 'network_supeq' }, + proname => 'network_supeq', prosupport => 'network_subset_support', + prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_supeq' }, +{ oid => '1173', descr => 'planner support for network_sub/superset', + proname => 'network_subset_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'network_subset_support' }, + { oid => '3551', proname => 'network_overlap', prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_overlap' }, @@ -5482,14 +5500,14 @@ prosrc => 'select $1::pg_catalog.text || $2' }, { oid => '2005', - proname => 'bytealike', prorettype => 'bool', proargtypes => 'bytea bytea', - prosrc => 'bytealike' }, + proname => 'bytealike', prosupport => 'textlike_support', + prorettype => 'bool', proargtypes => 'bytea bytea', prosrc => 'bytealike' }, { oid => '2006', proname => 'byteanlike', prorettype => 'bool', proargtypes => 'bytea bytea', prosrc => 'byteanlike' }, { oid => '2007', descr => 'matches LIKE expression', - proname => 'like', prorettype => 'bool', proargtypes => 'bytea bytea', - prosrc => 'bytealike' }, + proname => 'like', prosupport => 'textlike_support', prorettype => 'bool', + proargtypes => 'bytea bytea', prosrc => 'bytealike' }, { oid => '2008', descr => 'does not match LIKE expression', proname => 'notlike', prorettype => 'bool', proargtypes => 'bytea bytea', prosrc => 'byteanlike' }, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 453079a9e2..f9389257c6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -510,7 +510,8 @@ typedef enum NodeTag T_SupportRequestSimplify, /* in nodes/supportnodes.h */ T_SupportRequestSelectivity, /* in nodes/supportnodes.h */ T_SupportRequestCost, /* in nodes/supportnodes.h */ - T_SupportRequestRows /* in nodes/supportnodes.h */ + T_SupportRequestRows, /* in nodes/supportnodes.h */ + T_SupportRequestIndexCondition /* in nodes/supportnodes.h */ } NodeTag; /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c23c4304f3..616ec3b3db 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -764,7 +764,12 @@ typedef struct RelOptInfo * (by plancat.c), indrestrictinfo and predOK are set later, in * check_index_predicates(). */ -typedef struct IndexOptInfo +#ifndef HAVE_INDEXOPTINFO_TYPEDEF +typedef struct IndexOptInfo IndexOptInfo; +#define HAVE_INDEXOPTINFO_TYPEDEF 1 +#endif + +struct IndexOptInfo { NodeTag type; @@ -818,7 +823,7 @@ typedef struct IndexOptInfo bool amcanparallel; /* does AM support parallel scan? */ /* Rather than include amapi.h here, we declare amcostestimate like this */ void (*amcostestimate) (); /* AM's cost estimator */ -} IndexOptInfo; +}; /* * ForeignKeyOptInfo diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 1a3a36ba99..460d75bd2d 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -35,7 +35,8 @@ #include "nodes/primnodes.h" -struct PlannerInfo; /* avoid including relation.h here */ +struct PlannerInfo; /* avoid including pathnodes.h here */ +struct IndexOptInfo; struct SpecialJoinInfo; @@ -167,4 +168,75 @@ typedef struct SupportRequestRows double rows; /* number of rows expected to be returned */ } SupportRequestRows; +/* + * The IndexCondition request allows the support function to generate + * a directly-indexable condition based on a target function call that is + * not itself indexable. The target function call must appear at the top + * level of WHERE or JOIN/ON, so this applies only to functions returning + * boolean. + * + * The "node" argument is the parse node that is invoking the target function; + * currently this will always be a FuncExpr or OpExpr. The call is made + * only if at least one function argument matches an index column's variable + * or expression. "indexarg" identifies the matching argument (it's the + * argument's zero-based index in the node's args list). + * + * If the transformation is possible, return a List of directly-indexable + * condition expressions, else return NULL. (A List is used because it's + * sometimes useful to generate more than one indexable condition, such as + * when a LIKE with constant prefix gives rise to both >= and < conditions.) + * + * "Directly indexable" means that the condition must be directly executable + * by the index machinery. Typically this means that it is a binary OpExpr + * with the index column value on the left, a pseudo-constant on the right, + * and an operator that is in the index column's operator family. Other + * possibilities include RowCompareExpr, ScalarArrayOpExpr, and NullTest, + * depending on the index type; but those seem less likely to be useful for + * derived index conditions. "Pseudo-constant" means that the right-hand + * expression must not contain any volatile functions, nor any Vars of the + * table the index is for; use is_pseudo_constant_for_index() to check this. + * (Note: if the passed "node" is an OpExpr, the core planner already verified + * that the non-indexkey operand is pseudo-constant; but when the "node" + * is a FuncExpr, it does not check, since it doesn't know which of the + * function's arguments you might need to use in an index comparison value.) + * + * In many cases, an index condition can be generated but it is weaker than + * the function condition itself; for example, a LIKE with a constant prefix + * can produce an index range check based on the prefix, but we still need + * to execute the LIKE operator to verify the rest of the pattern. We say + * that such an index condition is "lossy". When returning an index condition, + * you should set the "lossy" request field to true if the condition is lossy, + * or false if it is an exact equivalent of the function's result. The core + * code will initialize that field to true, which is the common case. + * + * It is important to verify that the index operator family is the correct + * one for the condition you want to generate. Core support functions tend + * to use the known OID of a built-in opfamily for this, but extensions need + * to work harder, since their OIDs aren't fixed. A possibly workable + * answer for an index on an extension datatype is to verify the index AM's + * OID instead, and then assume that there's only one relevant opclass for + * your datatype so the opfamily must be the right one. Generating OpExpr + * nodes may also require knowing extension datatype OIDs (often you can + * find these out by applying exprType() to a function argument) and + * operator OIDs (which you can look up using get_opfamily_member). + */ +typedef struct SupportRequestIndexCondition +{ + NodeTag type; + + /* Input fields: */ + struct PlannerInfo *root; /* Planner's infrastructure */ + Oid funcid; /* function we are inquiring about */ + Node *node; /* parse node invoking function */ + int indexarg; /* index of function arg matching indexcol */ + struct IndexOptInfo *index; /* planner's info about target index */ + int indexcol; /* index of target index column (0-based) */ + Oid opfamily; /* index column's operator family */ + Oid indexcollation; /* index column's collation */ + + /* Output fields: */ + bool lossy; /* set to false if index condition is an exact + * equivalent of the function call */ +} SupportRequestIndexCondition; + #endif /* SUPPORTNODES_H */ diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index ffaf5b9450..ffd812a4ed 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -35,7 +35,11 @@ typedef struct PlannerInfo PlannerInfo; #define HAVE_PLANNERINFO_TYPEDEF 1 #endif -/* Likewise for SpecialJoinInfo. */ +/* Likewise for IndexOptInfo and SpecialJoinInfo. */ +#ifndef HAVE_INDEXOPTINFO_TYPEDEF +typedef struct IndexOptInfo IndexOptInfo; +#define HAVE_INDEXOPTINFO_TYPEDEF 1 +#endif #ifndef HAVE_SPECIALJOININFO_TYPEDEF typedef struct SpecialJoinInfo SpecialJoinInfo; #define HAVE_SPECIALJOININFO_TYPEDEF 1 @@ -74,6 +78,10 @@ extern PGDLLIMPORT int effective_cache_size; extern double clamp_row_est(double nrows); +/* in path/indxpath.c: */ + +extern bool is_pseudo_constant_for_index(Node *expr, IndexOptInfo *index); + /* in plan/planner.c: */ /* possible values for force_parallel_mode */ diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index 0bd48dc5a0..b21298a2a6 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -105,6 +105,15 @@ SELECT b.* set enable_seqscan to false; set enable_indexscan to true; set enable_bitmapscan to false; +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------ + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) + Filter: (proname ~~ 'RI\_FKey%del'::text) +(3 rows) + select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; proname ------------------------ @@ -115,8 +124,42 @@ select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; RI_FKey_setnull_del (5 rows) +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) + Filter: (proname ~~* '00%foo'::text) +(3 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) +(2 rows) + set enable_indexscan to false; set enable_bitmapscan to true; +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~ 'RI\_FKey%del'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) +(6 rows) + select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; proname ------------------------ @@ -127,6 +170,34 @@ select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; RI_FKey_setnull_del (5 rows) +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------------------- + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~* '00%foo'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) +(6 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) +(2 rows) + +reset enable_seqscan; +reset enable_indexscan; +reset enable_bitmapscan; -- -- Test B-tree page deletion. In particular, deleting a non-leaf page. -- diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 4932869c19..5d4eb59a0c 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -3201,6 +3201,24 @@ explain (costs off) Index Cond: (b = false) (3 rows) +explain (costs off) + select * from boolindex where b is true order by i desc limit 10; + QUERY PLAN +---------------------------------------------------------------- + Limit + -> Index Scan Backward using boolindex_b_i_key on boolindex + Index Cond: (b = true) +(3 rows) + +explain (costs off) + select * from boolindex where b is false order by i desc limit 10; + QUERY PLAN +---------------------------------------------------------------- + Limit + -> Index Scan Backward using boolindex_b_i_key on boolindex + Index Cond: (b = false) +(3 rows) + -- -- Test for multilevel page deletion -- diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out index be9427eb6b..24202376f1 100644 --- a/src/test/regress/expected/inet.out +++ b/src/test/regress/expected/inet.out @@ -242,6 +242,15 @@ SELECT '' AS ten, set_masklen(inet(text(i)), 24) FROM INET_TBL; -- check that btree index works correctly CREATE INDEX inet_idx1 ON inet_tbl(i); SET enable_seqscan TO off; +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr; + QUERY PLAN +------------------------------------------------------------------------------- + Index Scan using inet_idx1 on inet_tbl + Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet)) + Filter: (i << '192.168.1.0/24'::inet) +(3 rows) + SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr; c | i ----------------+------------------ @@ -250,6 +259,15 @@ SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr; 192.168.1.0/26 | 192.168.1.226 (3 rows) +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr; + QUERY PLAN +-------------------------------------------------------------------------------- + Index Scan using inet_idx1 on inet_tbl + Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet)) + Filter: (i <<= '192.168.1.0/24'::inet) +(3 rows) + SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr; c | i ----------------+------------------ @@ -261,6 +279,43 @@ SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr; 192.168.1.0/26 | 192.168.1.226 (6 rows) +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i; + QUERY PLAN +-------------------------------------------------------------------------------- + Index Scan using inet_idx1 on inet_tbl + Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet)) + Filter: ('192.168.1.0/24'::inet >>= i) +(3 rows) + +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i; + c | i +----------------+------------------ + 192.168.1.0/24 | 192.168.1.0/24 + 192.168.1.0/24 | 192.168.1.226/24 + 192.168.1.0/24 | 192.168.1.255/24 + 192.168.1.0/24 | 192.168.1.0/25 + 192.168.1.0/24 | 192.168.1.255/25 + 192.168.1.0/26 | 192.168.1.226 +(6 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i; + QUERY PLAN +------------------------------------------------------------------------------- + Index Scan using inet_idx1 on inet_tbl + Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet)) + Filter: ('192.168.1.0/24'::inet >> i) +(3 rows) + +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i; + c | i +----------------+------------------ + 192.168.1.0/24 | 192.168.1.0/25 + 192.168.1.0/24 | 192.168.1.255/25 + 192.168.1.0/26 | 192.168.1.226 +(3 rows) + SET enable_seqscan TO on; DROP INDEX inet_idx1; -- check that gist index works correctly diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 054faabb72..6ff2fd3274 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -294,6 +294,105 @@ order by thousand, tenthous; 999 | 9999 (25 rows) +explain (costs off) +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + QUERY PLAN +----------------------------------------------------------------------- + Sort + Sort Key: thousand, tenthous + -> Bitmap Heap Scan on tenk1 + Filter: (ROW(thousand, tenthous, four) > ROW(998, 5000, 3)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (ROW(thousand, tenthous) >= ROW(998, 5000)) +(6 rows) + +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + thousand | tenthous | four +----------+----------+------ + 998 | 5998 | 2 + 998 | 6998 | 2 + 998 | 7998 | 2 + 998 | 8998 | 2 + 998 | 9998 | 2 + 999 | 999 | 3 + 999 | 1999 | 3 + 999 | 2999 | 3 + 999 | 3999 | 3 + 999 | 4999 | 3 + 999 | 5999 | 3 + 999 | 6999 | 3 + 999 | 7999 | 3 + 999 | 8999 | 3 + 999 | 9999 | 3 +(15 rows) + +explain (costs off) +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + QUERY PLAN +---------------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (ROW(thousand, tenthous) > ROW(998, 5000)) +(2 rows) + +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + thousand | tenthous +----------+---------- + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 +(15 rows) + +explain (costs off) +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: thousand, hundred + -> Bitmap Heap Scan on tenk1 + Filter: (ROW(998, 5000) < ROW(thousand, hundred)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand >= 998) +(6 rows) + +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + thousand | hundred +----------+--------- + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 +(10 rows) + -- Test case for bug #14010: indexed row comparisons fail with nulls create temp table test_table (a text, b text); insert into test_table values ('a', 'b'); diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql index 21171f7762..2b087be796 100644 --- a/src/test/regress/sql/btree_index.sql +++ b/src/test/regress/sql/btree_index.sql @@ -59,11 +59,29 @@ SELECT b.* set enable_seqscan to false; set enable_indexscan to true; set enable_bitmapscan to false; +explain (costs off) select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; +select proname from pg_proc where proname ilike '00%foo' order by 1; +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; set enable_indexscan to false; set enable_bitmapscan to true; +explain (costs off) select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; +select proname from pg_proc where proname ilike '00%foo' order by 1; +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + +reset enable_seqscan; +reset enable_indexscan; +reset enable_bitmapscan; -- -- Test B-tree page deletion. In particular, deleting a non-leaf page. diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 59da6b6592..67ecad8dd5 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -1135,6 +1135,10 @@ explain (costs off) select * from boolindex where b = true order by i desc limit 10; explain (costs off) select * from boolindex where not b order by i limit 10; +explain (costs off) + select * from boolindex where b is true order by i desc limit 10; +explain (costs off) + select * from boolindex where b is false order by i desc limit 10; -- -- Test for multilevel page deletion diff --git a/src/test/regress/sql/inet.sql b/src/test/regress/sql/inet.sql index 880e115360..bbfa9d37df 100644 --- a/src/test/regress/sql/inet.sql +++ b/src/test/regress/sql/inet.sql @@ -65,8 +65,18 @@ SELECT '' AS ten, set_masklen(inet(text(i)), 24) FROM INET_TBL; -- check that btree index works correctly CREATE INDEX inet_idx1 ON inet_tbl(i); SET enable_seqscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr; +SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr; +EXPLAIN (COSTS OFF) SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr; +SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr; +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i; +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i; +EXPLAIN (COSTS OFF) +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i; +SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i; SET enable_seqscan TO on; DROP INDEX inet_idx1; diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 454d462078..ea93347caf 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -119,6 +119,33 @@ select thousand, tenthous from tenk1 where (thousand, tenthous) >= (997, 5000) order by thousand, tenthous; +explain (costs off) +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + +explain (costs off) +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + +explain (costs off) +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + -- Test case for bug #14010: indexed row comparisons fail with nulls create temp table test_table (a text, b text); insert into test_table values ('a', 'b');