Allow extensions to generate lossy index conditions.
authorTom Lane <[email protected]>
Tue, 12 Feb 2019 02:26:08 +0000 (21:26 -0500)
committerTom Lane <[email protected]>
Tue, 12 Feb 2019 02:26:14 +0000 (21:26 -0500)
For a long time, indxpath.c has had the ability to extract derived (lossy)
index conditions from certain operators such as LIKE.  For just as long,
it's been obvious that we really ought to make that capability available
to extensions.  This commit finally accomplishes that, by adding another
API for planner support functions that lets them create derived index
conditions for their functions.  As proof of concept, the hardwired
"special index operator" code formerly present in indxpath.c is pushed
out to planner support functions attached to LIKE and other relevant
operators.

A weak spot in this design is that an extension needs to know OIDs for
the operators, datatypes, and opfamilies involved in the transformation
it wants to make.  The core-code prototypes use hard-wired OID references
but extensions don't have that option for their own operators etc.  It's
usually possible to look up the required info, but that may be slow and
inconvenient.  However, improving that situation is a separate task.

I want to do some additional refactorization around selfuncs.c, but
that also seems like a separate task.

Discussion: https://postgr.es/m/15193.1548028093@sss.pgh.pa.us

20 files changed:
doc/src/sgml/xfunc.sgml
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/util/clauses.c
src/backend/utils/adt/Makefile
src/backend/utils/adt/like_support.c [new file with mode: 0644]
src/backend/utils/adt/network.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/nodes/nodes.h
src/include/nodes/pathnodes.h
src/include/nodes/supportnodes.h
src/include/optimizer/optimizer.h
src/test/regress/expected/btree_index.out
src/test/regress/expected/create_index.out
src/test/regress/expected/inet.out
src/test/regress/expected/rowtypes.out
src/test/regress/sql/btree_index.sql
src/test/regress/sql/create_index.sql
src/test/regress/sql/inet.sql
src/test/regress/sql/rowtypes.sql

index b486ef391eeb2c6ea8872f5d063e2796898ac1f0..34032698631d04eb495dc58d36e4b323c275d13e 100644 (file)
@@ -3460,4 +3460,18 @@ supportfn(internal) returns internal
     This can be done by a support function that implements
     the <literal>SupportRequestRows</literal> request type.
    </para>
+
+   <para>
+    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 <firstterm>lossy</firstterm>; 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 <literal>SupportRequestIndexCondition</literal> request type.
+   </para>
   </sect1>
index c03777ed6070e48fcf95412f615b65887f5d8e1f..2d5a09b1b458c7e50599df1a5b338c836509e7b0 100644 (file)
 #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,
+                            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,
-                            Oid opfamily,
-                            Oid idxcollation,
-                            RowCompareExpr *clause);
+                            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))
+   {
+       return match_opclause_to_indexcol(root, rinfo, indexcol, index);
+   }
+   else if (IsA(clause, FuncExpr))
    {
-       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_funcclause_to_indexcol(root, rinfo, indexcol, index);
    }
-   else if (clause && IsA(clause, ScalarArrayOpExpr))
+   else if (IsA(clause, ScalarArrayOpExpr))
    {
-       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_saopclause_to_indexcol(rinfo, indexcol, index);
    }
-   else if (clause && IsA(clause, RowCompareExpr))
+   else if (IsA(clause, RowCompareExpr))
    {
-       return match_rowcompare_to_indexcol(index, indexcol,
-                                           opfamily, idxcollation,
-                                           (RowCompareExpr *) clause);
+       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;
+       }
+   }
+
+   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;
    }
-   else
-       return false;
+
+   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, right_relids) &&
+       !bms_is_member(index_relid, rinfo->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))
+       {
+           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.
+        * If we didn't find a member of the index's opfamily, try the support
+        * function for the operator's underlying function.
         */
-       if (plain_op &&
-           match_special_index_operator(clause, opfamily,
-                                        idxcollation, true))
-           return true;
-       return false;
+       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 (plain_op &&
-       match_index_to_operand(rightop, indexcol, index) &&
-       !bms_is_member(index_relid, left_relids) &&
+   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) &&
-           is_indexable_operator(expr_op, opfamily, false))
-           return true;
+       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, see whether it
-        * is a "special" indexable operator.
+        * If we didn't find a member of the index's opfamily, try the support
+        * function for the operator's underlying function.
         */
-       if (match_special_index_operator(clause, opfamily,
-                                        idxcollation, false))
-           return true;
-       return false;
+       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 false;
+   return NULL;
 }
 
 /*
- * 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.
+ * match_funcclause_to_indexcol()
+ *   Handles the FuncExpr case for match_clause_to_indexcol(),
+ *   which see for comments.
  */
-static bool
-is_indexable_operator(Oid expr_op, Oid opfamily, bool indexkey_on_left)
+static IndexClause *
+match_funcclause_to_indexcol(PlannerInfo *root,
+                            RestrictInfo *rinfo,
+                            int indexcol,
+                            IndexOptInfo *index)
 {
-   /* Get the commuted operator if necessary */
-   if (!indexkey_on_left)
+   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)
    {
-       expr_op = get_commutator(expr_op);
-       if (expr_op == InvalidOid)
-           return false;
+       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) &&
+           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;
+       }
+
+       /*
+        * We do not currently ask support functions about ScalarArrayOpExprs,
+        * though in principle we could.
+        */
    }
 
-   /* 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,26 +2917,266 @@ 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;
 }
 
-
-/****************************************************************************
- *             ----  ROUTINES TO CHECK ORDERING OPERATORS  ----
- ****************************************************************************/
-
 /*
- * match_pathkeys_to_index
- *     Test whether an index can produce output ordered according to the
- *     given pathkeys using "ordering operators".
+ * expand_indexqual_rowcompare --- expand a single indexqual condition
+ *     that is a RowCompareExpr
  *
- * If it can, return a list of suitable ORDER BY expressions, each of the form
- * "indexedcol operator pseudoconstant", along with an integer list of the
- * index column numbers (zero based) that each clause would be used with.
- * NIL lists are returned if the ordering is not achievable this way.
+ * 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;
+}
+
+
+/****************************************************************************
+ *             ----  ROUTINES TO CHECK ORDERING OPERATORS  ----
+ ****************************************************************************/
+
+/*
+ * match_pathkeys_to_index
+ *     Test whether an index can produce output ordered according to the
+ *     given pathkeys using "ordering operators".
+ *
+ * If it can, return a list of suitable ORDER BY expressions, each of the form
+ * "indexedcol operator pseudoconstant", along with an integer list of the
+ * index column numbers (zero based) that each clause would be used with.
+ * NIL lists are returned if the ordering is not achievable this way.
  *
  * On success, the result list is ordered by pathkeys, and in fact is
  * one-to-one with the requested pathkeys.
@@ -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.)
- *
- * 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.)
- *
- * 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.)
- *
- * 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.
- */
-
-/*
- * 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)
-{
-   /* 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.
+ * is_pseudo_constant_for_index()
+ *   Test whether the given expression can be used as an indexscan
+ *   comparison value.
+ *
+ * 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().
+ *
+ * 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.
+ *
+ * expr: the nodetree to be checked
+ * index: the index of interest
  */
-static bool
-match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation,
-                            bool indexkey_on_left)
+bool
+is_pseudo_constant_for_index(Node *expr, IndexOptInfo *index)
 {
-   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), &ltproc);
-   greaterstr = make_greater_string(prefix_const, &ltproc, 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;
 }
index 86e4753a5b380b00cb9b2468db0ed1652f5ebe40..3737166e8f49965fddf87e7ca45bf4d49677e685 100644 (file)
@@ -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
index 20eead17983e41e7543b31deb2474ef7e508a8b4..82d10af752a7879ac8eb7af9e75e2c5dd6eb4deb 100644 (file)
@@ -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 (file)
index 0000000..b001dde
--- /dev/null
@@ -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), &ltproc);
+   greaterstr = make_greater_string(prefix, &ltproc, indexcollation);
+   if (greaterstr)
+   {
+       expr = make_opclause(oproid, BOOLOID, false,
+                            (Expr *) leftop, (Expr *) greaterstr,
+                            InvalidOid, indexcollation);
+       result = lappend(result, expr);
+   }
+
+   return result;
+}
index 5af7f4e0468eff8d86abf3b4cde85fcc2ac5de9b..7f3ca7f930d524d42f1b077efae1f6e1b31fcde3 100644 (file)
 #include <arpa/inet.h>
 
 #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.
  */
index 7a65fde762bb099d7d036d788a25e06390dcacf3..3f4e1997626be1d6c5f02c45d4d8399feece795b 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201902111
+#define CATALOG_VERSION_NO 201902112
 
 #endif
index 50b742c06e60b213d0e1bb7f1cfa4295c1853d1d..24f99f7fc459d4f98a31c35d1c9b343df5815465 100644 (file)
   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' },
   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' },
   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' },
   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' },
   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' },
   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' },
   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' },
   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' },
   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' },
index 453079a9e268caf7043cf4ae4689a2bbd18a3418..f9389257c60b52488b1e5c108a436dde58d10691 100644 (file)
@@ -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;
 
 /*
index c23c4304f375660596e487668ff824c724f78b27..616ec3b3dbf62b780ad1013bc90603edbd192f95 100644 (file)
@@ -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
index 1a3a36ba99ca0b50af474969e656c1ef972541a4..460d75bd2ddcc867c407c7800e9ef3a09f2d189c 100644 (file)
@@ -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 */
index ffaf5b9450b05ae9bf9d8e6e7fc0ba6cef3307fa..ffd812a4ed8ffbd21da27c16b6c3c92c0b589910 100644 (file)
@@ -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 */
index 0bd48dc5a0f661e22b235254e8e38100ebb3a67d..b21298a2a6b831489fb28b50e5a337b40a751890 100644 (file)
@@ -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.
 --
index 4932869c19cfcd0c63035a73bfd4f43ac8e7bb97..5d4eb59a0cea354d5ec2d0833bcdac853881757e 100644 (file)
@@ -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
 --
index be9427eb6b8178f7a095d1896207f9f9cb72145c..24202376f1989d8f337b1805e92c525cb840be44 100644 (file)
@@ -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
index 054faabb724534acb4b64204b653168d36d362c3..6ff2fd327427f77cb26f161a3bdbbcfbdc2a664f 100644 (file)
@@ -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');
index 21171f776259b55ee51d4e666c46cc8e6bdb1424..2b087be796c55ab82c7335c9872ede7f42cab843 100644 (file)
@@ -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.
index 59da6b6592669670fb8a0ed178052871f7d82453..67ecad8dd5e5add28e7046b6770b9646f7cee578 100644 (file)
@@ -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
index 880e115360d98356add13e2949d428ed4ddee035..bbfa9d37dfc59e40e1454aec4a817f7e0740ede7 100644 (file)
@@ -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;
 
index 454d462078e936a3e5c1e01a63c6c8f2379fe952..ea93347caf7475c49238f54db4377b6ea2f4af95 100644 (file)
@@ -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');