]> granicus.if.org Git - postgresql/commitdiff
Make use of statistics on index expressions. There are still some
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 17 Feb 2004 00:52:53 +0000 (00:52 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 17 Feb 2004 00:52:53 +0000 (00:52 +0000)
corner cases that could stand improvement, but it does all the basic
stuff.  A byproduct is that the selectivity routines are no longer
constrained to working on simple Vars; we might in future be able to
improve the behavior for subexpressions that don't match indexes.

src/backend/optimizer/path/costsize.c
src/backend/optimizer/util/relnode.c
src/backend/utils/adt/selfuncs.c
src/include/optimizer/pathnode.h
src/include/utils/selfuncs.h

index c23cf4d23246d5f3dd26bcd8ce02920c702c3eac..79674ac4b94881a74c2efe343459d07c6f40183d 100644 (file)
@@ -49,7 +49,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.124 2004/02/03 17:34:03 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.125 2004/02/17 00:52:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -102,8 +102,6 @@ bool                enable_mergejoin = true;
 bool           enable_hashjoin = true;
 
 
-static Selectivity estimate_hash_bucketsize(Query *root, Var *var,
-                                                int nbuckets);
 static bool cost_qual_eval_walker(Node *node, QualCost *total);
 static Selectivity approx_selectivity(Query *root, List *quals,
                                   JoinType jointype);
@@ -1152,7 +1150,7 @@ cost_hashjoin(HashPath *path, Query *root)
                                        /* not cached yet */
                                        thisbucketsize =
                                                estimate_hash_bucketsize(root,
-                                                          (Var *) get_rightop(restrictinfo->clause),
+                                                                                                get_rightop(restrictinfo->clause),
                                                                                                 virtualbuckets);
                                        restrictinfo->right_bucketsize = thisbucketsize;
                                }
@@ -1168,7 +1166,7 @@ cost_hashjoin(HashPath *path, Query *root)
                                        /* not cached yet */
                                        thisbucketsize =
                                                estimate_hash_bucketsize(root,
-                                                               (Var *) get_leftop(restrictinfo->clause),
+                                                                                                get_leftop(restrictinfo->clause),
                                                                                                 virtualbuckets);
                                        restrictinfo->left_bucketsize = thisbucketsize;
                                }
@@ -1249,179 +1247,6 @@ cost_hashjoin(HashPath *path, Query *root)
        path->jpath.path.total_cost = startup_cost + run_cost;
 }
 
-/*
- * Estimate hash bucketsize fraction (ie, number of entries in a bucket
- * divided by total tuples in relation) if the specified Var is used
- * as a hash key.
- *
- * XXX This is really pretty bogus since we're effectively assuming that the
- * distribution of hash keys will be the same after applying restriction
- * clauses as it was in the underlying relation.  However, we are not nearly
- * smart enough to figure out how the restrict clauses might change the
- * distribution, so this will have to do for now.
- *
- * We are passed the number of buckets the executor will use for the given
- * input relation.     If the data were perfectly distributed, with the same
- * number of tuples going into each available bucket, then the bucketsize
- * fraction would be 1/nbuckets.  But this happy state of affairs will occur
- * only if (a) there are at least nbuckets distinct data values, and (b)
- * we have a not-too-skewed data distribution. Otherwise the buckets will
- * be nonuniformly occupied.  If the other relation in the join has a key
- * distribution similar to this one's, then the most-loaded buckets are
- * exactly those that will be probed most often.  Therefore, the "average"
- * bucket size for costing purposes should really be taken as something close
- * to the "worst case" bucket size.  We try to estimate this by adjusting the
- * fraction if there are too few distinct data values, and then scaling up
- * by the ratio of the most common value's frequency to the average frequency.
- *
- * If no statistics are available, use a default estimate of 0.1.  This will
- * discourage use of a hash rather strongly if the inner relation is large,
- * which is what we want.  We do not want to hash unless we know that the
- * inner rel is well-dispersed (or the alternatives seem much worse).
- */
-static Selectivity
-estimate_hash_bucketsize(Query *root, Var *var, int nbuckets)
-{
-       Oid                     relid;
-       RelOptInfo *rel;
-       HeapTuple       tuple;
-       Form_pg_statistic stats;
-       double          estfract,
-                               ndistinct,
-                               mcvfreq,
-                               avgfreq;
-       float4     *numbers;
-       int                     nnumbers;
-
-       /* Ignore any binary-compatible relabeling */
-       if (var && IsA(var, RelabelType))
-               var = (Var *) ((RelabelType *) var)->arg;
-
-       /*
-        * Lookup info about var's relation and attribute; if none available,
-        * return default estimate.
-        */
-       if (var == NULL || !IsA(var, Var))
-               return 0.1;
-
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               return 0.1;
-
-       rel = find_base_rel(root, var->varno);
-
-       if (rel->tuples <= 0.0 || rel->rows <= 0.0)
-               return 0.1;                             /* ensure we can divide below */
-
-       tuple = SearchSysCache(STATRELATT,
-                                                  ObjectIdGetDatum(relid),
-                                                  Int16GetDatum(var->varattno),
-                                                  0, 0);
-       if (!HeapTupleIsValid(tuple))
-       {
-               /*
-                * If the attribute is known unique because of an index,
-                * we can treat it as well-distributed.
-                */
-               if (has_unique_index(rel, var->varattno))
-                       return 1.0 / (double) nbuckets;
-
-               /*
-                * Perhaps the Var is a system attribute; if so, it will have no
-                * entry in pg_statistic, but we may be able to guess something
-                * about its distribution anyway.
-                */
-               switch (var->varattno)
-               {
-                       case ObjectIdAttributeNumber:
-                       case SelfItemPointerAttributeNumber:
-                               /* these are unique, so buckets should be well-distributed */
-                               return 1.0 / (double) nbuckets;
-                       case TableOidAttributeNumber:
-                               /* hashing this is a terrible idea... */
-                               return 1.0;
-               }
-               return 0.1;
-       }
-       stats = (Form_pg_statistic) GETSTRUCT(tuple);
-
-       /*
-        * Obtain number of distinct data values in raw relation.
-        */
-       ndistinct = stats->stadistinct;
-       if (ndistinct < 0.0)
-               ndistinct = -ndistinct * rel->tuples;
-
-       if (ndistinct <= 0.0)           /* ensure we can divide */
-       {
-               ReleaseSysCache(tuple);
-               return 0.1;
-       }
-
-       /* Also compute avg freq of all distinct data values in raw relation */
-       avgfreq = (1.0 - stats->stanullfrac) / ndistinct;
-
-       /*
-        * Adjust ndistinct to account for restriction clauses.  Observe we
-        * are assuming that the data distribution is affected uniformly by
-        * the restriction clauses!
-        *
-        * XXX Possibly better way, but much more expensive: multiply by
-        * selectivity of rel's restriction clauses that mention the target
-        * Var.
-        */
-       ndistinct *= rel->rows / rel->tuples;
-
-       /*
-        * Initial estimate of bucketsize fraction is 1/nbuckets as long as
-        * the number of buckets is less than the expected number of distinct
-        * values; otherwise it is 1/ndistinct.
-        */
-       if (ndistinct > (double) nbuckets)
-               estfract = 1.0 / (double) nbuckets;
-       else
-               estfract = 1.0 / ndistinct;
-
-       /*
-        * Look up the frequency of the most common value, if available.
-        */
-       mcvfreq = 0.0;
-
-       if (get_attstatsslot(tuple, var->vartype, var->vartypmod,
-                                                STATISTIC_KIND_MCV, InvalidOid,
-                                                NULL, NULL, &numbers, &nnumbers))
-       {
-               /*
-                * The first MCV stat is for the most common value.
-                */
-               if (nnumbers > 0)
-                       mcvfreq = numbers[0];
-               free_attstatsslot(var->vartype, NULL, 0,
-                                                 numbers, nnumbers);
-       }
-
-       /*
-        * Adjust estimated bucketsize upward to account for skewed
-        * distribution.
-        */
-       if (avgfreq > 0.0 && mcvfreq > avgfreq)
-               estfract *= mcvfreq / avgfreq;
-
-       /*
-        * Clamp bucketsize to sane range (the above adjustment could easily
-        * produce an out-of-range result).  We set the lower bound a little
-        * above zero, since zero isn't a very sane result.
-        */
-       if (estfract < 1.0e-6)
-               estfract = 1.0e-6;
-       else if (estfract > 1.0)
-               estfract = 1.0;
-
-       ReleaseSysCache(tuple);
-
-       return (Selectivity) estfract;
-}
-
 
 /*
  * cost_qual_eval
index d6d093ea467666acddfbbbfde4f4114c50eac3c4..d5a5480c62e94a3c37411c724db60b9691ff836c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.54 2003/12/08 18:19:58 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.55 2004/02/17 00:52:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -214,12 +214,8 @@ find_base_rel(Query *root, int relid)
  * find_join_rel
  *       Returns relation entry corresponding to 'relids' (a set of RT indexes),
  *       or NULL if none exists.  This is for join relations.
- *
- * Note: there is probably no good reason for this to be called from
- * anywhere except build_join_rel, but keep it as a separate routine
- * just in case.
  */
-static RelOptInfo *
+RelOptInfo *
 find_join_rel(Query *root, Relids relids)
 {
        List       *joinrels;
index 84f18dc935993a70a20459a4bfe4df0314223f05..054739140970a5de8805e59ee5f37e293d2778bc 100644 (file)
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.156 2004/02/02 03:07:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.157 2004/02/17 00:52:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /* default selectivity estimate for boolean and null test nodes */
 #define DEFAULT_UNK_SEL                        0.005
 #define DEFAULT_NOT_UNK_SEL            (1.0 - DEFAULT_UNK_SEL)
-#define DEFAULT_BOOL_SEL               0.5
 
 /*
  * Clamp a computed probability estimate (which may suffer from roundoff or
        } while (0)
 
 
-static bool get_var_maximum(Query *root, Var *var, Oid sortop, Datum *max);
+/* Return data from examine_variable and friends */
+typedef struct
+{
+       Node       *var;                        /* the Var or expression tree */
+       RelOptInfo *rel;                        /* Relation, or NULL if not identifiable */
+       HeapTuple       statsTuple;             /* pg_statistic tuple, or NULL if none */
+       /* NB: if statsTuple!=NULL, it must be freed when caller is done */
+       Oid                     atttype;                /* type to pass to get_attstatsslot */
+       int32           atttypmod;              /* typmod to pass to get_attstatsslot */
+       bool            isunique;               /* true if matched to a unique index */
+} VariableStatData;
+
+#define ReleaseVariableStats(vardata)  \
+       do { \
+               if (HeapTupleIsValid((vardata).statsTuple)) \
+                       ReleaseSysCache((vardata).statsTuple); \
+       } while(0)
+
+
 static bool convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue,
                                  Datum lobound, Datum hibound, Oid boundstypid,
                                  double *scaledlobound, double *scaledhibound);
@@ -174,13 +191,18 @@ static double convert_one_bytea_to_scalar(unsigned char *value, int valuelen,
                                                        int rangelo, int rangehi);
 static unsigned char *convert_string_datum(Datum value, Oid typid);
 static double convert_timevalue_to_scalar(Datum value, Oid typid);
-static double get_att_numdistinct(Query *root, Var *var,
-                                       Form_pg_statistic stats);
-static bool get_restriction_var(List *args, int varRelid,
-                                       Var **var, Node **other,
+static bool get_restriction_variable(Query *root, List *args, int varRelid,
+                                       VariableStatData *vardata, Node **other,
                                        bool *varonleft);
-static void get_join_vars(List *args, Var **var1, Var **var2);
-static Selectivity prefix_selectivity(Query *root, Var *var,
+static void get_join_variables(Query *root, List *args,
+                                                          VariableStatData *vardata1,
+                                                          VariableStatData *vardata2);
+static void examine_variable(Query *root, Node *node, int varRelid,
+                                                        VariableStatData *vardata);
+static double get_variable_numdistinct(VariableStatData *vardata);
+static bool get_variable_maximum(Query *root, VariableStatData *vardata,
+                                                                Oid sortop, Datum *max);
+static Selectivity prefix_selectivity(Query *root, VariableStatData *vardata,
                                   Oid opclass, Const *prefix);
 static Selectivity pattern_selectivity(Const *patt, Pattern_Type ptype);
 static Datum string_to_datum(const char *str, Oid datatype);
@@ -203,11 +225,9 @@ eqsel(PG_FUNCTION_ARGS)
        Oid                     operator = PG_GETARG_OID(1);
        List       *args = (List *) PG_GETARG_POINTER(2);
        int                     varRelid = PG_GETARG_INT32(3);
-       Var                *var;
+       VariableStatData vardata;
        Node       *other;
        bool            varonleft;
-       Oid                     relid;
-       HeapTuple       statsTuple;
        Datum      *values;
        int                     nvalues;
        float4     *numbers;
@@ -215,15 +235,11 @@ eqsel(PG_FUNCTION_ARGS)
        double          selec;
 
        /*
-        * If expression is not var = something or something = var for a
-        * simple var of a real relation (no subqueries, for now), then punt
-        * and return a default estimate.
+        * If expression is not variable = something or something = variable,
+        * then punt and return a default estimate.
         */
-       if (!get_restriction_var(args, varRelid,
-                                                        &var, &other, &varonleft))
-               PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
+       if (!get_restriction_variable(root, args, varRelid,
+                                                                 &vardata, &other, &varonleft))
                PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
 
        /*
@@ -232,22 +248,20 @@ eqsel(PG_FUNCTION_ARGS)
         */
        if (IsA(other, Const) &&
                ((Const *) other)->constisnull)
+       {
+               ReleaseVariableStats(vardata);
                PG_RETURN_FLOAT8(0.0);
+       }
 
-       /* get stats for the attribute, if available */
-       statsTuple = SearchSysCache(STATRELATT,
-                                                               ObjectIdGetDatum(relid),
-                                                               Int16GetDatum(var->varattno),
-                                                               0, 0);
-       if (HeapTupleIsValid(statsTuple))
+       if (HeapTupleIsValid(vardata.statsTuple))
        {
                Form_pg_statistic stats;
 
-               stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
+               stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
 
                if (IsA(other, Const))
                {
-                       /* Var is being compared to a known non-null constant */
+                       /* Variable is being compared to a known non-null constant */
                        Datum           constval = ((Const *) other)->constvalue;
                        bool            match = false;
                        int                     i;
@@ -259,7 +273,8 @@ eqsel(PG_FUNCTION_ARGS)
                         * an appropriate test.  If you don't like this, maybe you
                         * shouldn't be using eqsel for your operator...)
                         */
-                       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
+                       if (get_attstatsslot(vardata.statsTuple,
+                                                                vardata.atttype, vardata.atttypmod,
                                                                 STATISTIC_KIND_MCV, InvalidOid,
                                                                 &values, &nvalues,
                                                                 &numbers, &nnumbers))
@@ -321,7 +336,7 @@ eqsel(PG_FUNCTION_ARGS)
                                 * remaining fraction equally, so we divide by the number
                                 * of other distinct values.
                                 */
-                               otherdistinct = get_att_numdistinct(root, var, stats)
+                               otherdistinct = get_variable_numdistinct(&vardata)
                                        - nnumbers;
                                if (otherdistinct > 1)
                                        selec /= otherdistinct;
@@ -334,7 +349,7 @@ eqsel(PG_FUNCTION_ARGS)
                                        selec = numbers[nnumbers - 1];
                        }
 
-                       free_attstatsslot(var->vartype, values, nvalues,
+                       free_attstatsslot(vardata.atttype, values, nvalues,
                                                          numbers, nnumbers);
                }
                else
@@ -352,7 +367,7 @@ eqsel(PG_FUNCTION_ARGS)
                         * frequency in the table.      Is that a good idea?)
                         */
                        selec = 1.0 - stats->stanullfrac;
-                       ndistinct = get_att_numdistinct(root, var, stats);
+                       ndistinct = get_variable_numdistinct(&vardata);
                        if (ndistinct > 1)
                                selec /= ndistinct;
 
@@ -360,18 +375,17 @@ eqsel(PG_FUNCTION_ARGS)
                         * Cross-check: selectivity should never be estimated as more
                         * than the most common value's.
                         */
-                       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
+                       if (get_attstatsslot(vardata.statsTuple,
+                                                                vardata.atttype, vardata.atttypmod,
                                                                 STATISTIC_KIND_MCV, InvalidOid,
                                                                 NULL, NULL,
                                                                 &numbers, &nnumbers))
                        {
                                if (nnumbers > 0 && selec > numbers[0])
                                        selec = numbers[0];
-                               free_attstatsslot(var->vartype, NULL, 0, numbers, nnumbers);
+                               free_attstatsslot(vardata.atttype, NULL, 0, numbers, nnumbers);
                        }
                }
-
-               ReleaseSysCache(statsTuple);
        }
        else
        {
@@ -381,9 +395,11 @@ eqsel(PG_FUNCTION_ARGS)
                 * equally common.      (The guess is unlikely to be very good, but we
                 * do know a few special cases.)
                 */
-               selec = 1.0 / get_att_numdistinct(root, var, NULL);
+               selec = 1.0 / get_variable_numdistinct(&vardata);
        }
 
+       ReleaseVariableStats(vardata);
+
        /* result should be in range, but make sure... */
        CLAMP_PROBABILITY(selec);
 
@@ -433,7 +449,7 @@ neqsel(PG_FUNCTION_ARGS)
  *     scalarineqsel           - Selectivity of "<", "<=", ">", ">=" for scalars.
  *
  * This is the guts of both scalarltsel and scalargtsel.  The caller has
- * commuted the clause, if necessary, so that we can treat the Var as
+ * commuted the clause, if necessary, so that we can treat the variable as
  * being on the left.  The caller must also make sure that the other side
  * of the clause is a non-null Const, and dissect same into a value and
  * datatype.
@@ -444,10 +460,8 @@ neqsel(PG_FUNCTION_ARGS)
  */
 static double
 scalarineqsel(Query *root, Oid operator, bool isgt,
-                         Var *var, Datum constval, Oid consttype)
+                         VariableStatData *vardata, Datum constval, Oid consttype)
 {
-       Oid                     relid;
-       HeapTuple       statsTuple;
        Form_pg_statistic stats;
        FmgrInfo        opproc;
        Datum      *values;
@@ -460,26 +474,12 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
        double          selec;
        int                     i;
 
-       /*
-        * If expression is not var op something or something op var for a
-        * simple var of a real relation (no subqueries, for now), then punt
-        * and return a default estimate.
-        */
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               return DEFAULT_INEQ_SEL;
-
-       /* get stats for the attribute */
-       statsTuple = SearchSysCache(STATRELATT,
-                                                               ObjectIdGetDatum(relid),
-                                                               Int16GetDatum(var->varattno),
-                                                               0, 0);
-       if (!HeapTupleIsValid(statsTuple))
+       if (!HeapTupleIsValid(vardata->statsTuple))
        {
                /* no stats available, so default result */
                return DEFAULT_INEQ_SEL;
        }
-       stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
+       stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
 
        fmgr_info(get_opcode(operator), &opproc);
 
@@ -492,7 +492,8 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
        mcv_selec = 0.0;
        sumcommon = 0.0;
 
-       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
+       if (get_attstatsslot(vardata->statsTuple,
+                                                vardata->atttype, vardata->atttypmod,
                                                 STATISTIC_KIND_MCV, InvalidOid,
                                                 &values, &nvalues,
                                                 &numbers, &nnumbers))
@@ -505,7 +506,8 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
                                mcv_selec += numbers[i];
                        sumcommon += numbers[i];
                }
-               free_attstatsslot(var->vartype, values, nvalues, numbers, nnumbers);
+               free_attstatsslot(vardata->atttype, values, nvalues,
+                                                 numbers, nnumbers);
        }
 
        /*
@@ -523,7 +525,8 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
         */
        hist_selec = 0.0;
 
-       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
+       if (get_attstatsslot(vardata->statsTuple,
+                                                vardata->atttype, vardata->atttypmod,
                                                 STATISTIC_KIND_HISTOGRAM, InvalidOid,
                                                 &values, &nvalues,
                                                 NULL, NULL))
@@ -582,7 +585,7 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
                                         */
                                        if (convert_to_scalar(constval, consttype, &val,
                                                                                  values[i - 1], values[i],
-                                                                                 var->vartype,
+                                                                                 vardata->atttype,
                                                                                  &low, &high))
                                        {
                                                if (high <= low)
@@ -653,7 +656,7 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
                                hist_selec = 0.9999;
                }
 
-               free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
+               free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
        }
 
        /*
@@ -676,8 +679,6 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
 
        selec += mcv_selec;
 
-       ReleaseSysCache(statsTuple);
-
        /* result should be in range, but make sure... */
        CLAMP_PROBABILITY(selec);
 
@@ -694,21 +695,20 @@ scalarltsel(PG_FUNCTION_ARGS)
        Oid                     operator = PG_GETARG_OID(1);
        List       *args = (List *) PG_GETARG_POINTER(2);
        int                     varRelid = PG_GETARG_INT32(3);
-       Var                *var;
+       VariableStatData vardata;
        Node       *other;
+       bool            varonleft;
        Datum           constval;
        Oid                     consttype;
-       bool            varonleft;
        bool            isgt;
        double          selec;
 
        /*
-        * If expression is not var op something or something op var for a
-        * simple var of a real relation (no subqueries, for now), then punt
-        * and return a default estimate.
+        * If expression is not variable op something or something op variable,
+        * then punt and return a default estimate.
         */
-       if (!get_restriction_var(args, varRelid,
-                                                        &var, &other, &varonleft))
+       if (!get_restriction_variable(root, args, varRelid,
+                                                                 &vardata, &other, &varonleft))
                PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
 
        /*
@@ -716,14 +716,20 @@ scalarltsel(PG_FUNCTION_ARGS)
         * either.
         */
        if (!IsA(other, Const))
+       {
+               ReleaseVariableStats(vardata);
                PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
+       }
 
        /*
         * If the constant is NULL, assume operator is strict and return zero,
         * ie, operator will never return TRUE.
         */
        if (((Const *) other)->constisnull)
+       {
+               ReleaseVariableStats(vardata);
                PG_RETURN_FLOAT8(0.0);
+       }
        constval = ((Const *) other)->constvalue;
        consttype = ((Const *) other)->consttype;
 
@@ -742,12 +748,15 @@ scalarltsel(PG_FUNCTION_ARGS)
                if (!operator)
                {
                        /* Use default selectivity (should we raise an error instead?) */
+                       ReleaseVariableStats(vardata);
                        PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
                }
                isgt = true;
        }
 
-       selec = scalarineqsel(root, operator, isgt, var, constval, consttype);
+       selec = scalarineqsel(root, operator, isgt, &vardata, constval, consttype);
+
+       ReleaseVariableStats(vardata);
 
        PG_RETURN_FLOAT8((float8) selec);
 }
@@ -762,21 +771,20 @@ scalargtsel(PG_FUNCTION_ARGS)
        Oid                     operator = PG_GETARG_OID(1);
        List       *args = (List *) PG_GETARG_POINTER(2);
        int                     varRelid = PG_GETARG_INT32(3);
-       Var                *var;
+       VariableStatData vardata;
        Node       *other;
+       bool            varonleft;
        Datum           constval;
        Oid                     consttype;
-       bool            varonleft;
        bool            isgt;
        double          selec;
 
        /*
-        * If expression is not var op something or something op var for a
-        * simple var of a real relation (no subqueries, for now), then punt
-        * and return a default estimate.
+        * If expression is not variable op something or something op variable,
+        * then punt and return a default estimate.
         */
-       if (!get_restriction_var(args, varRelid,
-                                                        &var, &other, &varonleft))
+       if (!get_restriction_variable(root, args, varRelid,
+                                                                 &vardata, &other, &varonleft))
                PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
 
        /*
@@ -784,14 +792,20 @@ scalargtsel(PG_FUNCTION_ARGS)
         * either.
         */
        if (!IsA(other, Const))
+       {
+               ReleaseVariableStats(vardata);
                PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
+       }
 
        /*
         * If the constant is NULL, assume operator is strict and return zero,
         * ie, operator will never return TRUE.
         */
        if (((Const *) other)->constisnull)
+       {
+               ReleaseVariableStats(vardata);
                PG_RETURN_FLOAT8(0.0);
+       }
        constval = ((Const *) other)->constvalue;
        consttype = ((Const *) other)->consttype;
 
@@ -810,12 +824,15 @@ scalargtsel(PG_FUNCTION_ARGS)
                if (!operator)
                {
                        /* Use default selectivity (should we raise an error instead?) */
+                       ReleaseVariableStats(vardata);
                        PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
                }
                isgt = false;
        }
 
-       selec = scalarineqsel(root, operator, isgt, var, constval, consttype);
+       selec = scalarineqsel(root, operator, isgt, &vardata, constval, consttype);
+
+       ReleaseVariableStats(vardata);
 
        PG_RETURN_FLOAT8((float8) selec);
 }
@@ -833,10 +850,9 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
 #endif
        List       *args = (List *) PG_GETARG_POINTER(2);
        int                     varRelid = PG_GETARG_INT32(3);
-       Var                *var;
+       VariableStatData vardata;
        Node       *other;
        bool            varonleft;
-       Oid                     relid;
        Datum           constval;
        Oid                     consttype;
        Oid                     vartype;
@@ -848,25 +864,27 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
        double          result;
 
        /*
-        * If expression is not var op constant for a simple var of a real
-        * relation (no subqueries, for now), then punt and return a default
-        * estimate.
+        * If expression is not variable op constant, then punt and return a
+        * default estimate.
         */
-       if (!get_restriction_var(args, varRelid,
-                                                        &var, &other, &varonleft))
+       if (!get_restriction_variable(root, args, varRelid,
+                                                                 &vardata, &other, &varonleft))
                return DEFAULT_MATCH_SEL;
        if (!varonleft || !IsA(other, Const))
+       {
+               ReleaseVariableStats(vardata);
                return DEFAULT_MATCH_SEL;
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               return DEFAULT_MATCH_SEL;
+       }
 
        /*
         * If the constant is NULL, assume operator is strict and return zero,
         * ie, operator will never return TRUE.
         */
        if (((Const *) other)->constisnull)
+       {
+               ReleaseVariableStats(vardata);
                return 0.0;
+       }
        constval = ((Const *) other)->constvalue;
        consttype = ((Const *) other)->consttype;
 
@@ -877,14 +895,17 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
         * match the operator's declared type.
         */
        if (consttype != TEXTOID && consttype != BYTEAOID)
+       {
+               ReleaseVariableStats(vardata);
                return DEFAULT_MATCH_SEL;
+       }
 
        /*
         * The var, on the other hand, might be a binary-compatible type;
         * particularly a domain.  Try to fold it if it's not recognized
         * immediately.
         */
-       vartype = var->vartype;
+       vartype = vardata.atttype;
        if (vartype != consttype)
                vartype = getBaseType(vartype);
 
@@ -915,6 +936,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
                        opclass = BYTEA_BTREE_OPS_OID;
                        break;
                default:
+                       ReleaseVariableStats(vardata);
                        return DEFAULT_MATCH_SEL;
        }
 
@@ -943,6 +965,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
                        default:
                                elog(ERROR, "unrecognized consttype: %u",
                                         prefix->consttype);
+                               ReleaseVariableStats(vardata);
                                return DEFAULT_MATCH_SEL;
                }
                prefix = string_to_const(prefixstr, vartype);
@@ -960,7 +983,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
 
                if (eqopr == InvalidOid)
                        elog(ERROR, "no = operator for opclass %u", opclass);
-               eqargs = makeList2(var, prefix);
+               eqargs = makeList2(vardata.var, prefix);
                result = DatumGetFloat8(DirectFunctionCall4(eqsel,
                                                                                                        PointerGetDatum(root),
                                                                                                 ObjectIdGetDatum(eqopr),
@@ -979,7 +1002,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
                Selectivity selec;
 
                if (pstatus == Pattern_Prefix_Partial)
-                       prefixsel = prefix_selectivity(root, var, opclass, prefix);
+                       prefixsel = prefix_selectivity(root, &vardata, opclass, prefix);
                else
                        prefixsel = 1.0;
                restsel = pattern_selectivity(rest, ptype);
@@ -995,6 +1018,8 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
                pfree(prefix);
        }
 
+       ReleaseVariableStats(vardata);
+
        return result;
 }
 
@@ -1093,80 +1118,25 @@ Selectivity
 booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                        int varRelid, JoinType jointype)
 {
-       Var                *var;
-       Oid                     relid;
-       HeapTuple       statsTuple;
-       Datum      *values;
-       int                     nvalues;
-       float4     *numbers;
-       int                     nnumbers;
+       VariableStatData vardata;
        double          selec;
 
-       /*
-        * Ignore any binary-compatible relabeling (probably unnecessary, but
-        * can't hurt)
-        */
-       if (IsA(arg, RelabelType))
-               arg = (Node *) ((RelabelType *) arg)->arg;
-
-       if (IsA(arg, Var) &&
-               (varRelid == 0 || varRelid == ((Var *) arg)->varno))
-               var = (Var *) arg;
-       else
-       {
-               /*
-                * If argument is not a Var, we can't get statistics for it, but
-                * perhaps clause_selectivity can do something with it.  We ignore
-                * the possibility of a NULL value when using clause_selectivity,
-                * and just assume the value is either TRUE or FALSE.
-                */
-               switch (booltesttype)
-               {
-                       case IS_UNKNOWN:
-                               selec = DEFAULT_UNK_SEL;
-                               break;
-                       case IS_NOT_UNKNOWN:
-                               selec = DEFAULT_NOT_UNK_SEL;
-                               break;
-                       case IS_TRUE:
-                       case IS_NOT_FALSE:
-                               selec = (double) clause_selectivity(root, arg,
-                                                                                                       varRelid, jointype);
-                               break;
-                       case IS_FALSE:
-                       case IS_NOT_TRUE:
-                               selec = 1.0 - (double) clause_selectivity(root, arg,
-                                                                                                        varRelid, jointype);
-                               break;
-                       default:
-                               elog(ERROR, "unrecognized booltesttype: %d",
-                                        (int) booltesttype);
-                               selec = 0.0;    /* Keep compiler quiet */
-                               break;
-               }
-               return (Selectivity) selec;
-       }
-
-       /* get stats for the attribute, if available */
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               statsTuple = NULL;
-       else
-               statsTuple = SearchSysCache(STATRELATT,
-                                                                       ObjectIdGetDatum(relid),
-                                                                       Int16GetDatum(var->varattno),
-                                                                       0, 0);
+       examine_variable(root, arg, varRelid, &vardata);
 
-       if (HeapTupleIsValid(statsTuple))
+       if (HeapTupleIsValid(vardata.statsTuple))
        {
                Form_pg_statistic stats;
                double          freq_null;
+               Datum      *values;
+               int                     nvalues;
+               float4     *numbers;
+               int                     nnumbers;
 
-               stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
-
+               stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
                freq_null = stats->stanullfrac;
 
-               if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
+               if (get_attstatsslot(vardata.statsTuple,
+                                                        vardata.atttype, vardata.atttypmod,
                                                         STATISTIC_KIND_MCV, InvalidOid,
                                                         &values, &nvalues,
                                                         &numbers, &nnumbers)
@@ -1184,7 +1154,7 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                                freq_true = 1.0 - numbers[0] - freq_null;
 
                        /*
-                        * Next derive freqency for false. Then use these as
+                        * Next derive frequency for false. Then use these as
                         * appropriate to derive frequency for each case.
                         */
                        freq_false = 1.0 - freq_true - freq_null;
@@ -1222,7 +1192,7 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                                        break;
                        }
 
-                       free_attstatsslot(var->vartype, values, nvalues,
+                       free_attstatsslot(vardata.atttype, values, nvalues,
                                                          numbers, nnumbers);
                }
                else
@@ -1263,14 +1233,14 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                                        break;
                        }
                }
-
-               ReleaseSysCache(statsTuple);
        }
        else
        {
                /*
-                * No VACUUM ANALYZE stats available, so use a default value.
-                * (Note: not much point in recursing to clause_selectivity here.)
+                * If we can't get variable statistics for the argument, perhaps
+                * clause_selectivity can do something with it.  We ignore
+                * the possibility of a NULL value when using clause_selectivity,
+                * and just assume the value is either TRUE or FALSE.
                 */
                switch (booltesttype)
                {
@@ -1281,10 +1251,14 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                                selec = DEFAULT_NOT_UNK_SEL;
                                break;
                        case IS_TRUE:
-                       case IS_NOT_TRUE:
-                       case IS_FALSE:
                        case IS_NOT_FALSE:
-                               selec = DEFAULT_BOOL_SEL;
+                               selec = (double) clause_selectivity(root, arg,
+                                                                                                       varRelid, jointype);
+                               break;
+                       case IS_FALSE:
+                       case IS_NOT_TRUE:
+                               selec = 1.0 - (double) clause_selectivity(root, arg,
+                                                                                                                 varRelid, jointype);
                                break;
                        default:
                                elog(ERROR, "unrecognized booltesttype: %d",
@@ -1294,6 +1268,8 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
                }
        }
 
+       ReleaseVariableStats(vardata);
+
        /* result should be in range, but make sure... */
        CLAMP_PROBABILITY(selec);
 
@@ -1306,56 +1282,17 @@ booltestsel(Query *root, BoolTestType booltesttype, Node *arg,
 Selectivity
 nulltestsel(Query *root, NullTestType nulltesttype, Node *arg, int varRelid)
 {
-       Var                *var;
-       Oid                     relid;
-       HeapTuple       statsTuple;
+       VariableStatData vardata;
        double          selec;
-       double          defselec;
-       double          freq_null;
-
-       switch (nulltesttype)
-       {
-               case IS_NULL:
-                       defselec = DEFAULT_UNK_SEL;
-                       break;
-               case IS_NOT_NULL:
-                       defselec = DEFAULT_NOT_UNK_SEL;
-                       break;
-               default:
-                       elog(ERROR, "unrecognized nulltesttype: %d",
-                                (int) nulltesttype);
-                       return (Selectivity) 0;         /* keep compiler quiet */
-       }
-
-       /*
-        * Ignore any binary-compatible relabeling
-        */
-       if (IsA(arg, RelabelType))
-               arg = (Node *) ((RelabelType *) arg)->arg;
-
-       if (IsA(arg, Var) &&
-               (varRelid == 0 || varRelid == ((Var *) arg)->varno))
-               var = (Var *) arg;
-       else
-       {
-               /* punt if non-Var argument */
-               return (Selectivity) defselec;
-       }
 
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               return (Selectivity) defselec;
+       examine_variable(root, arg, varRelid, &vardata);
 
-       /* get stats for the attribute, if available */
-       statsTuple = SearchSysCache(STATRELATT,
-                                                               ObjectIdGetDatum(relid),
-                                                               Int16GetDatum(var->varattno),
-                                                               0, 0);
-       if (HeapTupleIsValid(statsTuple))
+       if (HeapTupleIsValid(vardata.statsTuple))
        {
                Form_pg_statistic stats;
+               double          freq_null;
 
-               stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
+               stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
                freq_null = stats->stanullfrac;
 
                switch (nulltesttype)
@@ -1380,17 +1317,29 @@ nulltestsel(Query *root, NullTestType nulltesttype, Node *arg, int varRelid)
                                         (int) nulltesttype);
                                return (Selectivity) 0; /* keep compiler quiet */
                }
-
-               ReleaseSysCache(statsTuple);
        }
        else
        {
                /*
                 * No VACUUM ANALYZE stats available, so make a guess
                 */
-               selec = defselec;
+               switch (nulltesttype)
+               {
+                       case IS_NULL:
+                               selec = DEFAULT_UNK_SEL;
+                               break;
+                       case IS_NOT_NULL:
+                               selec = DEFAULT_NOT_UNK_SEL;
+                               break;
+                       default:
+                               elog(ERROR, "unrecognized nulltesttype: %d",
+                                        (int) nulltesttype);
+                               return (Selectivity) 0;         /* keep compiler quiet */
+               }
        }
 
+       ReleaseVariableStats(vardata);
+
        /* result should be in range, but make sure... */
        CLAMP_PROBABILITY(selec);
 
@@ -1407,293 +1356,257 @@ eqjoinsel(PG_FUNCTION_ARGS)
        Oid                     operator = PG_GETARG_OID(1);
        List       *args = (List *) PG_GETARG_POINTER(2);
        JoinType        jointype = (JoinType) PG_GETARG_INT16(3);
-       Var                *var1;
-       Var                *var2;
        double          selec;
+       VariableStatData vardata1;
+       VariableStatData vardata2;
+       double          nd1;
+       double          nd2;
+       Form_pg_statistic stats1 = NULL;
+       Form_pg_statistic stats2 = NULL;
+       bool            have_mcvs1 = false;
+       Datum      *values1 = NULL;
+       int                     nvalues1 = 0;
+       float4     *numbers1 = NULL;
+       int                     nnumbers1 = 0;
+       bool            have_mcvs2 = false;
+       Datum      *values2 = NULL;
+       int                     nvalues2 = 0;
+       float4     *numbers2 = NULL;
+       int                     nnumbers2 = 0;
+
+       get_join_variables(root, args, &vardata1, &vardata2);
+
+       nd1 = get_variable_numdistinct(&vardata1);
+       nd2 = get_variable_numdistinct(&vardata2);
+
+       if (HeapTupleIsValid(vardata1.statsTuple))
+       {
+               stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple);
+               have_mcvs1 = get_attstatsslot(vardata1.statsTuple,
+                                                                         vardata1.atttype,
+                                                                         vardata1.atttypmod,
+                                                                         STATISTIC_KIND_MCV,
+                                                                         InvalidOid,
+                                                                         &values1, &nvalues1,
+                                                                         &numbers1, &nnumbers1);
+       }
 
-       get_join_vars(args, &var1, &var2);
+       if (HeapTupleIsValid(vardata2.statsTuple))
+       {
+               stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple);
+               have_mcvs2 = get_attstatsslot(vardata2.statsTuple,
+                                                                         vardata2.atttype,
+                                                                         vardata2.atttypmod,
+                                                                         STATISTIC_KIND_MCV,
+                                                                         InvalidOid,
+                                                                         &values2, &nvalues2,
+                                                                         &numbers2, &nnumbers2);
+       }
 
-       if (var1 == NULL && var2 == NULL)
-               selec = DEFAULT_EQ_SEL;
-       else
+       if (have_mcvs1 && have_mcvs2)
        {
-               HeapTuple       statsTuple1 = NULL;
-               HeapTuple       statsTuple2 = NULL;
-               Form_pg_statistic stats1 = NULL;
-               Form_pg_statistic stats2 = NULL;
-               double          nd1 = DEFAULT_NUM_DISTINCT;
-               double          nd2 = DEFAULT_NUM_DISTINCT;
-               bool            have_mcvs1 = false;
-               Datum      *values1 = NULL;
-               int                     nvalues1 = 0;
-               float4     *numbers1 = NULL;
-               int                     nnumbers1 = 0;
-               bool            have_mcvs2 = false;
-               Datum      *values2 = NULL;
-               int                     nvalues2 = 0;
-               float4     *numbers2 = NULL;
-               int                     nnumbers2 = 0;
-
-               if (var1 != NULL)
-               {
-                       /* get stats for the attribute, if available */
-                       Oid                     relid1 = getrelid(var1->varno, root->rtable);
+               /*
+                * We have most-common-value lists for both relations.  Run
+                * through the lists to see which MCVs actually join to each
+                * other with the given operator.  This allows us to determine
+                * the exact join selectivity for the portion of the relations
+                * represented by the MCV lists.  We still have to estimate
+                * for the remaining population, but in a skewed distribution
+                * this gives us a big leg up in accuracy.      For motivation see
+                * the analysis in Y. Ioannidis and S. Christodoulakis, "On
+                * the propagation of errors in the size of join results",
+                * Technical Report 1018, Computer Science Dept., University
+                * of Wisconsin, Madison, March 1991 (available from
+                * ftp.cs.wisc.edu).
+                */
+               FmgrInfo        eqproc;
+               bool       *hasmatch1;
+               bool       *hasmatch2;
+               double          nullfrac1 = stats1->stanullfrac;
+               double          nullfrac2 = stats2->stanullfrac;
+               double          matchprodfreq,
+                                       matchfreq1,
+                                       matchfreq2,
+                                       unmatchfreq1,
+                                       unmatchfreq2,
+                                       otherfreq1,
+                                       otherfreq2,
+                                       totalsel1,
+                                       totalsel2;
+               int                     i,
+                                       nmatches;
+
+               fmgr_info(get_opcode(operator), &eqproc);
+               hasmatch1 = (bool *) palloc0(nvalues1 * sizeof(bool));
+               hasmatch2 = (bool *) palloc0(nvalues2 * sizeof(bool));
 
-                       if (relid1 != InvalidOid)
-                       {
-                               statsTuple1 = SearchSysCache(STATRELATT,
-                                                                                        ObjectIdGetDatum(relid1),
-                                                                                  Int16GetDatum(var1->varattno),
-                                                                                        0, 0);
-                               if (HeapTupleIsValid(statsTuple1))
-                               {
-                                       stats1 = (Form_pg_statistic) GETSTRUCT(statsTuple1);
-                                       have_mcvs1 = get_attstatsslot(statsTuple1,
-                                                                                                 var1->vartype,
-                                                                                                 var1->vartypmod,
-                                                                                                 STATISTIC_KIND_MCV,
-                                                                                                 InvalidOid,
-                                                                                                 &values1, &nvalues1,
-                                                                                                 &numbers1, &nnumbers1);
-                               }
+               /*
+                * If we are doing any variant of JOIN_IN, pretend all the
+                * values of the righthand relation are unique (ie, act as if
+                * it's been DISTINCT'd).
+                *
+                * NOTE: it might seem that we should unique-ify the lefthand
+                * input when considering JOIN_REVERSE_IN.      But this is not
+                * so, because the join clause we've been handed has not been
+                * commuted from the way the parser originally wrote it.  We
+                * know that the unique side of the IN clause is *always* on
+                * the right.
+                *
+                * NOTE: it would be dangerous to try to be smart about JOIN_LEFT
+                * or JOIN_RIGHT here, because we do not have enough
+                * information to determine which var is really on which side
+                * of the join. Perhaps someday we should pass in more
+                * information.
+                */
+               if (jointype == JOIN_IN ||
+                       jointype == JOIN_REVERSE_IN ||
+                       jointype == JOIN_UNIQUE_INNER ||
+                       jointype == JOIN_UNIQUE_OUTER)
+               {
+                       float4          oneovern = 1.0 / nd2;
 
-                               nd1 = get_att_numdistinct(root, var1, stats1);
-                       }
+                       for (i = 0; i < nvalues2; i++)
+                               numbers2[i] = oneovern;
+                       nullfrac2 = oneovern;
                }
 
-               if (var2 != NULL)
+               /*
+                * Note we assume that each MCV will match at most one member
+                * of the other MCV list.  If the operator isn't really
+                * equality, there could be multiple matches --- but we don't
+                * look for them, both for speed and because the math wouldn't
+                * add up...
+                */
+               matchprodfreq = 0.0;
+               nmatches = 0;
+               for (i = 0; i < nvalues1; i++)
                {
-                       /* get stats for the attribute, if available */
-                       Oid                     relid2 = getrelid(var2->varno, root->rtable);
+                       int                     j;
 
-                       if (relid2 != InvalidOid)
+                       for (j = 0; j < nvalues2; j++)
                        {
-                               statsTuple2 = SearchSysCache(STATRELATT,
-                                                                                        ObjectIdGetDatum(relid2),
-                                                                                  Int16GetDatum(var2->varattno),
-                                                                                        0, 0);
-                               if (HeapTupleIsValid(statsTuple2))
+                               if (hasmatch2[j])
+                                       continue;
+                               if (DatumGetBool(FunctionCall2(&eqproc,
+                                                                                          values1[i],
+                                                                                          values2[j])))
                                {
-                                       stats2 = (Form_pg_statistic) GETSTRUCT(statsTuple2);
-                                       have_mcvs2 = get_attstatsslot(statsTuple2,
-                                                                                                 var2->vartype,
-                                                                                                 var2->vartypmod,
-                                                                                                 STATISTIC_KIND_MCV,
-                                                                                                 InvalidOid,
-                                                                                                 &values2, &nvalues2,
-                                                                                                 &numbers2, &nnumbers2);
+                                       hasmatch1[i] = hasmatch2[j] = true;
+                                       matchprodfreq += numbers1[i] * numbers2[j];
+                                       nmatches++;
+                                       break;
                                }
-
-                               nd2 = get_att_numdistinct(root, var2, stats2);
                        }
                }
-
-               if (have_mcvs1 && have_mcvs2)
+               CLAMP_PROBABILITY(matchprodfreq);
+               /* Sum up frequencies of matched and unmatched MCVs */
+               matchfreq1 = unmatchfreq1 = 0.0;
+               for (i = 0; i < nvalues1; i++)
                {
-                       /*
-                        * We have most-common-value lists for both relations.  Run
-                        * through the lists to see which MCVs actually join to each
-                        * other with the given operator.  This allows us to determine
-                        * the exact join selectivity for the portion of the relations
-                        * represented by the MCV lists.  We still have to estimate
-                        * for the remaining population, but in a skewed distribution
-                        * this gives us a big leg up in accuracy.      For motivation see
-                        * the analysis in Y. Ioannidis and S. Christodoulakis, "On
-                        * the propagation of errors in the size of join results",
-                        * Technical Report 1018, Computer Science Dept., University
-                        * of Wisconsin, Madison, March 1991 (available from
-                        * ftp.cs.wisc.edu).
-                        */
-                       FmgrInfo        eqproc;
-                       bool       *hasmatch1;
-                       bool       *hasmatch2;
-                       double          nullfrac1 = stats1->stanullfrac;
-                       double          nullfrac2 = stats2->stanullfrac;
-                       double          matchprodfreq,
-                                               matchfreq1,
-                                               matchfreq2,
-                                               unmatchfreq1,
-                                               unmatchfreq2,
-                                               otherfreq1,
-                                               otherfreq2,
-                                               totalsel1,
-                                               totalsel2;
-                       int                     i,
-                                               nmatches;
-
-                       fmgr_info(get_opcode(operator), &eqproc);
-                       hasmatch1 = (bool *) palloc0(nvalues1 * sizeof(bool));
-                       hasmatch2 = (bool *) palloc0(nvalues2 * sizeof(bool));
-
-                       /*
-                        * If we are doing any variant of JOIN_IN, pretend all the
-                        * values of the righthand relation are unique (ie, act as if
-                        * it's been DISTINCT'd).
-                        *
-                        * NOTE: it might seem that we should unique-ify the lefthand
-                        * input when considering JOIN_REVERSE_IN.      But this is not
-                        * so, because the join clause we've been handed has not been
-                        * commuted from the way the parser originally wrote it.  We
-                        * know that the unique side of the IN clause is *always* on
-                        * the right.
-                        *
-                        * NOTE: it would be dangerous to try to be smart about JOIN_LEFT
-                        * or JOIN_RIGHT here, because we do not have enough
-                        * information to determine which var is really on which side
-                        * of the join. Perhaps someday we should pass in more
-                        * information.
-                        */
-                       if (jointype == JOIN_IN ||
-                               jointype == JOIN_REVERSE_IN ||
-                               jointype == JOIN_UNIQUE_INNER ||
-                               jointype == JOIN_UNIQUE_OUTER)
-                       {
-                               float4          oneovern = 1.0 / nd2;
-
-                               for (i = 0; i < nvalues2; i++)
-                                       numbers2[i] = oneovern;
-                               nullfrac2 = oneovern;
-                       }
-
-                       /*
-                        * Note we assume that each MCV will match at most one member
-                        * of the other MCV list.  If the operator isn't really
-                        * equality, there could be multiple matches --- but we don't
-                        * look for them, both for speed and because the math wouldn't
-                        * add up...
-                        */
-                       matchprodfreq = 0.0;
-                       nmatches = 0;
-                       for (i = 0; i < nvalues1; i++)
-                       {
-                               int                     j;
+                       if (hasmatch1[i])
+                               matchfreq1 += numbers1[i];
+                       else
+                               unmatchfreq1 += numbers1[i];
+               }
+               CLAMP_PROBABILITY(matchfreq1);
+               CLAMP_PROBABILITY(unmatchfreq1);
+               matchfreq2 = unmatchfreq2 = 0.0;
+               for (i = 0; i < nvalues2; i++)
+               {
+                       if (hasmatch2[i])
+                               matchfreq2 += numbers2[i];
+                       else
+                               unmatchfreq2 += numbers2[i];
+               }
+               CLAMP_PROBABILITY(matchfreq2);
+               CLAMP_PROBABILITY(unmatchfreq2);
+               pfree(hasmatch1);
+               pfree(hasmatch2);
 
-                               for (j = 0; j < nvalues2; j++)
-                               {
-                                       if (hasmatch2[j])
-                                               continue;
-                                       if (DatumGetBool(FunctionCall2(&eqproc,
-                                                                                                  values1[i],
-                                                                                                  values2[j])))
-                                       {
-                                               hasmatch1[i] = hasmatch2[j] = true;
-                                               matchprodfreq += numbers1[i] * numbers2[j];
-                                               nmatches++;
-                                               break;
-                                       }
-                               }
-                       }
-                       CLAMP_PROBABILITY(matchprodfreq);
-                       /* Sum up frequencies of matched and unmatched MCVs */
-                       matchfreq1 = unmatchfreq1 = 0.0;
-                       for (i = 0; i < nvalues1; i++)
-                       {
-                               if (hasmatch1[i])
-                                       matchfreq1 += numbers1[i];
-                               else
-                                       unmatchfreq1 += numbers1[i];
-                       }
-                       CLAMP_PROBABILITY(matchfreq1);
-                       CLAMP_PROBABILITY(unmatchfreq1);
-                       matchfreq2 = unmatchfreq2 = 0.0;
-                       for (i = 0; i < nvalues2; i++)
-                       {
-                               if (hasmatch2[i])
-                                       matchfreq2 += numbers2[i];
-                               else
-                                       unmatchfreq2 += numbers2[i];
-                       }
-                       CLAMP_PROBABILITY(matchfreq2);
-                       CLAMP_PROBABILITY(unmatchfreq2);
-                       pfree(hasmatch1);
-                       pfree(hasmatch2);
+               /*
+                * Compute total frequency of non-null values that are not in
+                * the MCV lists.
+                */
+               otherfreq1 = 1.0 - nullfrac1 - matchfreq1 - unmatchfreq1;
+               otherfreq2 = 1.0 - nullfrac2 - matchfreq2 - unmatchfreq2;
+               CLAMP_PROBABILITY(otherfreq1);
+               CLAMP_PROBABILITY(otherfreq2);
 
-                       /*
-                        * Compute total frequency of non-null values that are not in
-                        * the MCV lists.
-                        */
-                       otherfreq1 = 1.0 - nullfrac1 - matchfreq1 - unmatchfreq1;
-                       otherfreq2 = 1.0 - nullfrac2 - matchfreq2 - unmatchfreq2;
-                       CLAMP_PROBABILITY(otherfreq1);
-                       CLAMP_PROBABILITY(otherfreq2);
+               /*
+                * We can estimate the total selectivity from the point of
+                * view of relation 1 as: the known selectivity for matched
+                * MCVs, plus unmatched MCVs that are assumed to match against
+                * random members of relation 2's non-MCV population, plus
+                * non-MCV values that are assumed to match against random
+                * members of relation 2's unmatched MCVs plus non-MCV values.
+                */
+               totalsel1 = matchprodfreq;
+               if (nd2 > nvalues2)
+                       totalsel1 += unmatchfreq1 * otherfreq2 / (nd2 - nvalues2);
+               if (nd2 > nmatches)
+                       totalsel1 += otherfreq1 * (otherfreq2 + unmatchfreq2) /
+                               (nd2 - nmatches);
+               /* Same estimate from the point of view of relation 2. */
+               totalsel2 = matchprodfreq;
+               if (nd1 > nvalues1)
+                       totalsel2 += unmatchfreq2 * otherfreq1 / (nd1 - nvalues1);
+               if (nd1 > nmatches)
+                       totalsel2 += otherfreq2 * (otherfreq1 + unmatchfreq1) /
+                               (nd1 - nmatches);
 
-                       /*
-                        * We can estimate the total selectivity from the point of
-                        * view of relation 1 as: the known selectivity for matched
-                        * MCVs, plus unmatched MCVs that are assumed to match against
-                        * random members of relation 2's non-MCV population, plus
-                        * non-MCV values that are assumed to match against random
-                        * members of relation 2's unmatched MCVs plus non-MCV values.
-                        */
-                       totalsel1 = matchprodfreq;
-                       if (nd2 > nvalues2)
-                               totalsel1 += unmatchfreq1 * otherfreq2 / (nd2 - nvalues2);
-                       if (nd2 > nmatches)
-                               totalsel1 += otherfreq1 * (otherfreq2 + unmatchfreq2) /
-                                       (nd2 - nmatches);
-                       /* Same estimate from the point of view of relation 2. */
-                       totalsel2 = matchprodfreq;
-                       if (nd1 > nvalues1)
-                               totalsel2 += unmatchfreq2 * otherfreq1 / (nd1 - nvalues1);
-                       if (nd1 > nmatches)
-                               totalsel2 += otherfreq2 * (otherfreq1 + unmatchfreq1) /
-                                       (nd1 - nmatches);
+               /*
+                * Use the smaller of the two estimates.  This can be
+                * justified in essentially the same terms as given below for
+                * the no-stats case: to a first approximation, we are
+                * estimating from the point of view of the relation with
+                * smaller nd.
+                */
+               selec = (totalsel1 < totalsel2) ? totalsel1 : totalsel2;
+       }
+       else
+       {
+               /*
+                * We do not have MCV lists for both sides.  Estimate the join
+                * selectivity as
+                * MIN(1/nd1,1/nd2)*(1-nullfrac1)*(1-nullfrac2). This is
+                * plausible if we assume that the join operator is strict and
+                * the non-null values are about equally distributed: a given
+                * non-null tuple of rel1 will join to either zero or
+                * N2*(1-nullfrac2)/nd2 rows of rel2, so total join rows are
+                * at most N1*(1-nullfrac1)*N2*(1-nullfrac2)/nd2 giving a join
+                * selectivity of not more than
+                * (1-nullfrac1)*(1-nullfrac2)/nd2. By the same logic it is
+                * not more than (1-nullfrac1)*(1-nullfrac2)/nd1, so the
+                * expression with MIN() is an upper bound.  Using the MIN()
+                * means we estimate from the point of view of the relation
+                * with smaller nd (since the larger nd is determining the
+                * MIN).  It is reasonable to assume that most tuples in this
+                * rel will have join partners, so the bound is probably
+                * reasonably tight and should be taken as-is.
+                *
+                * XXX Can we be smarter if we have an MCV list for just one
+                * side? It seems that if we assume equal distribution for the
+                * other side, we end up with the same answer anyway.
+                */
+               double          nullfrac1 = stats1 ? stats1->stanullfrac : 0.0;
+               double          nullfrac2 = stats2 ? stats2->stanullfrac : 0.0;
 
-                       /*
-                        * Use the smaller of the two estimates.  This can be
-                        * justified in essentially the same terms as given below for
-                        * the no-stats case: to a first approximation, we are
-                        * estimating from the point of view of the relation with
-                        * smaller nd.
-                        */
-                       selec = (totalsel1 < totalsel2) ? totalsel1 : totalsel2;
-               }
+               selec = (1.0 - nullfrac1) * (1.0 - nullfrac2);
+               if (nd1 > nd2)
+                       selec /= nd1;
                else
-               {
-                       /*
-                        * We do not have MCV lists for both sides.  Estimate the join
-                        * selectivity as
-                        * MIN(1/nd1,1/nd2)*(1-nullfrac1)*(1-nullfrac2). This is
-                        * plausible if we assume that the join operator is strict and
-                        * the non-null values are about equally distributed: a given
-                        * non-null tuple of rel1 will join to either zero or
-                        * N2*(1-nullfrac2)/nd2 rows of rel2, so total join rows are
-                        * at most N1*(1-nullfrac1)*N2*(1-nullfrac2)/nd2 giving a join
-                        * selectivity of not more than
-                        * (1-nullfrac1)*(1-nullfrac2)/nd2. By the same logic it is
-                        * not more than (1-nullfrac1)*(1-nullfrac2)/nd1, so the
-                        * expression with MIN() is an upper bound.  Using the MIN()
-                        * means we estimate from the point of view of the relation
-                        * with smaller nd (since the larger nd is determining the
-                        * MIN).  It is reasonable to assume that most tuples in this
-                        * rel will have join partners, so the bound is probably
-                        * reasonably tight and should be taken as-is.
-                        *
-                        * XXX Can we be smarter if we have an MCV list for just one
-                        * side? It seems that if we assume equal distribution for the
-                        * other side, we end up with the same answer anyway.
-                        */
-                       double          nullfrac1 = stats1 ? stats1->stanullfrac : 0.0;
-                       double          nullfrac2 = stats2 ? stats2->stanullfrac : 0.0;
+                       selec /= nd2;
+       }
 
-                       selec = (1.0 - nullfrac1) * (1.0 - nullfrac2);
-                       if (nd1 > nd2)
-                               selec /= nd1;
-                       else
-                               selec /= nd2;
-               }
+       if (have_mcvs1)
+               free_attstatsslot(vardata1.atttype, values1, nvalues1,
+                                                 numbers1, nnumbers1);
+       if (have_mcvs2)
+               free_attstatsslot(vardata2.atttype, values2, nvalues2,
+                                                 numbers2, nnumbers2);
 
-               if (have_mcvs1)
-                       free_attstatsslot(var1->vartype, values1, nvalues1,
-                                                         numbers1, nnumbers1);
-               if (have_mcvs2)
-                       free_attstatsslot(var2->vartype, values2, nvalues2,
-                                                         numbers2, nnumbers2);
-               if (HeapTupleIsValid(statsTuple1))
-                       ReleaseSysCache(statsTuple1);
-               if (HeapTupleIsValid(statsTuple2))
-                       ReleaseSysCache(statsTuple2);
-       }
+       ReleaseVariableStats(vardata1);
+       ReleaseVariableStats(vardata2);
 
        CLAMP_PROBABILITY(selec);
 
@@ -1860,8 +1773,10 @@ mergejoinscansel(Query *root, Node *clause,
                                 Selectivity *leftscan,
                                 Selectivity *rightscan)
 {
-       Var                *left,
+       Node       *left,
                           *right;
+       VariableStatData leftvar,
+                               rightvar;
        Oid                     lefttype,
                                righttype;
        Oid                     opno,
@@ -1883,42 +1798,31 @@ mergejoinscansel(Query *root, Node *clause,
        if (!is_opclause(clause))
                return;                                 /* shouldn't happen */
        opno = ((OpExpr *) clause)->opno;
-       left = (Var *) get_leftop((Expr *) clause);
-       right = (Var *) get_rightop((Expr *) clause);
+       left = get_leftop((Expr *) clause);
+       right = get_rightop((Expr *) clause);
        if (!right)
                return;                                 /* shouldn't happen */
 
-       /* Save the direct input types of the operator */
-       lefttype = exprType((Node *) left);
-       righttype = exprType((Node *) right);
+       /* Look for stats for the inputs */
+       examine_variable(root, left, 0, &leftvar);
+       examine_variable(root, right, 0, &rightvar);
 
-       /*
-        * Now skip any binary-compatible relabeling; there can only be one
-        * level since constant-expression folder eliminates adjacent
-        * RelabelTypes.
-        */
-       if (IsA(left, RelabelType))
-               left = (Var *) ((RelabelType *) left)->arg;
-       if (IsA(right, RelabelType))
-               right = (Var *) ((RelabelType *) right)->arg;
-
-       /* Can't do anything if inputs are not Vars */
-       if (!IsA(left, Var) ||
-               !IsA(right, Var))
-               return;
+       /* Get the direct input types of the operator */
+       lefttype = exprType(left);
+       righttype = exprType(right);
 
        /* Verify mergejoinability and get left and right "<" operators */
        if (!op_mergejoinable(opno,
                                                  &lsortop,
                                                  &rsortop))
-               return;                                 /* shouldn't happen */
+               goto fail;                              /* shouldn't happen */
 
-       /* Try to get maximum values of both vars */
-       if (!get_var_maximum(root, left, lsortop, &leftmax))
-               return;                                 /* no max available from stats */
+       /* Try to get maximum values of both inputs */
+       if (!get_variable_maximum(root, &leftvar, lsortop, &leftmax))
+               goto fail;                              /* no max available from stats */
 
-       if (!get_var_maximum(root, right, rsortop, &rightmax))
-               return;                                 /* no max available from stats */
+       if (!get_variable_maximum(root, &rightvar, rsortop, &rightmax))
+               goto fail;                              /* no max available from stats */
 
        /* Look up the "left < right" and "left > right" operators */
        op_mergejoin_crossops(opno, &ltop, &gtop, NULL, NULL);
@@ -1926,30 +1830,30 @@ mergejoinscansel(Query *root, Node *clause,
        /* Look up the "left <= right" operator */
        leop = get_negator(gtop);
        if (!OidIsValid(leop))
-               return;                                 /* insufficient info in catalogs */
+               goto fail;                              /* insufficient info in catalogs */
 
        /* Look up the "right > left" operator */
        revgtop = get_commutator(ltop);
        if (!OidIsValid(revgtop))
-               return;                                 /* insufficient info in catalogs */
+               goto fail;                              /* insufficient info in catalogs */
 
        /* Look up the "right <= left" operator */
        revleop = get_negator(revgtop);
        if (!OidIsValid(revleop))
-               return;                                 /* insufficient info in catalogs */
+               goto fail;                              /* insufficient info in catalogs */
 
        /*
         * Now, the fraction of the left variable that will be scanned is the
         * fraction that's <= the right-side maximum value.  But only believe
         * non-default estimates, else stick with our 1.0.
         */
-       selec = scalarineqsel(root, leop, false, left,
+       selec = scalarineqsel(root, leop, false, &leftvar,
                                                  rightmax, righttype);
        if (selec != DEFAULT_INEQ_SEL)
                *leftscan = selec;
 
        /* And similarly for the right variable. */
-       selec = scalarineqsel(root, revleop, false, right,
+       selec = scalarineqsel(root, revleop, false, &rightvar,
                                                  leftmax, lefttype);
        if (selec != DEFAULT_INEQ_SEL)
                *rightscan = selec;
@@ -1966,6 +1870,10 @@ mergejoinscansel(Query *root, Node *clause,
                *rightscan = 1.0;
        else
                *leftscan = *rightscan = 1.0;
+
+fail:
+       ReleaseVariableStats(leftvar);
+       ReleaseVariableStats(rightvar);
 }
 
 /*
@@ -2076,25 +1984,14 @@ estimate_num_groups(Query *root, List *groupExprs, double input_rows)
        foreach(l, allvars)
        {
                Var                *var = (Var *) lfirst(l);
-               Oid                     relid = getrelid(var->varno, root->rtable);
-               HeapTuple       statsTuple = NULL;
-               Form_pg_statistic stats = NULL;
+               VariableStatData vardata;
                double          ndistinct;
                bool            keep = true;
                List       *l2;
 
-               if (OidIsValid(relid))
-               {
-                       statsTuple = SearchSysCache(STATRELATT,
-                                                                               ObjectIdGetDatum(relid),
-                                                                               Int16GetDatum(var->varattno),
-                                                                               0, 0);
-                       if (HeapTupleIsValid(statsTuple))
-                               stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
-               }
-               ndistinct = get_att_numdistinct(root, var, stats);
-               if (HeapTupleIsValid(statsTuple))
-                       ReleaseSysCache(statsTuple);
+               examine_variable(root, (Node *) var, 0, &vardata);
+               ndistinct = get_variable_numdistinct(&vardata);
+               ReleaseVariableStats(vardata);
 
                /* cannot use foreach here because of possible lremove */
                l2 = varinfos;
@@ -2201,143 +2098,152 @@ estimate_num_groups(Query *root, List *groupExprs, double input_rows)
        return numdistinct;
 }
 
-
-/*-------------------------------------------------------------------------
+/*
+ * Estimate hash bucketsize fraction (ie, number of entries in a bucket
+ * divided by total tuples in relation) if the specified expression is used
+ * as a hash key.
  *
- * Support routines
+ * XXX This is really pretty bogus since we're effectively assuming that the
+ * distribution of hash keys will be the same after applying restriction
+ * clauses as it was in the underlying relation.  However, we are not nearly
+ * smart enough to figure out how the restrict clauses might change the
+ * distribution, so this will have to do for now.
  *
- *-------------------------------------------------------------------------
- */
-
-/*
- * get_var_maximum
- *             Estimate the maximum value of the specified variable.
- *             If successful, store value in *max and return TRUE.
- *             If no data available, return FALSE.
+ * We are passed the number of buckets the executor will use for the given
+ * input relation.     If the data were perfectly distributed, with the same
+ * number of tuples going into each available bucket, then the bucketsize
+ * fraction would be 1/nbuckets.  But this happy state of affairs will occur
+ * only if (a) there are at least nbuckets distinct data values, and (b)
+ * we have a not-too-skewed data distribution. Otherwise the buckets will
+ * be nonuniformly occupied.  If the other relation in the join has a key
+ * distribution similar to this one's, then the most-loaded buckets are
+ * exactly those that will be probed most often.  Therefore, the "average"
+ * bucket size for costing purposes should really be taken as something close
+ * to the "worst case" bucket size.  We try to estimate this by adjusting the
+ * fraction if there are too few distinct data values, and then scaling up
+ * by the ratio of the most common value's frequency to the average frequency.
  *
- * sortop is the "<" comparison operator to use.  (To extract the
- * minimum instead of the maximum, just pass the ">" operator instead.)
+ * If no statistics are available, use a default estimate of 0.1.  This will
+ * discourage use of a hash rather strongly if the inner relation is large,
+ * which is what we want.  We do not want to hash unless we know that the
+ * inner rel is well-dispersed (or the alternatives seem much worse).
  */
-static bool
-get_var_maximum(Query *root, Var *var, Oid sortop, Datum *max)
+Selectivity
+estimate_hash_bucketsize(Query *root, Node *hashkey, int nbuckets)
 {
-       Datum           tmax = 0;
-       bool            have_max = false;
-       Oid                     relid;
-       HeapTuple       statsTuple;
-       Form_pg_statistic stats;
-       int16           typLen;
-       bool            typByVal;
-       Datum      *values;
-       int                     nvalues;
-       int                     i;
+       VariableStatData vardata;
+       double          estfract,
+                               ndistinct,
+                               stanullfrac,
+                               mcvfreq,
+                               avgfreq;
+       float4     *numbers;
+       int                     nnumbers;
 
-       relid = getrelid(var->varno, root->rtable);
-       if (relid == InvalidOid)
-               return false;
+       examine_variable(root, hashkey, 0, &vardata);
 
-       /* get stats for the attribute */
-       statsTuple = SearchSysCache(STATRELATT,
-                                                               ObjectIdGetDatum(relid),
-                                                               Int16GetDatum(var->varattno),
-                                                               0, 0);
-       if (!HeapTupleIsValid(statsTuple))
+       /* Get number of distinct values and fraction that are null */
+       ndistinct = get_variable_numdistinct(&vardata);
+
+       if (HeapTupleIsValid(vardata.statsTuple))
        {
-               /* no stats available, so default result */
-               return false;
+               Form_pg_statistic stats;
+
+               stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
+               stanullfrac = stats->stanullfrac;
+       }
+       else
+       {
+               /*
+                * Believe a default ndistinct only if it came from stats.
+                * Otherwise punt and return 0.1, per comments above.
+                */
+               if (ndistinct == DEFAULT_NUM_DISTINCT)
+               {
+                       ReleaseVariableStats(vardata);
+                       return (Selectivity) 0.1;
+               }
+
+               stanullfrac = 0.0;
        }
-       stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
 
-       get_typlenbyval(var->vartype, &typLen, &typByVal);
+       /* Compute avg freq of all distinct data values in raw relation */
+       avgfreq = (1.0 - stanullfrac) / ndistinct;
 
        /*
-        * If there is a histogram, grab the last or first value as
-        * appropriate.
+        * Adjust ndistinct to account for restriction clauses.  Observe we
+        * are assuming that the data distribution is affected uniformly by
+        * the restriction clauses!
         *
-        * If there is a histogram that is sorted with some other operator than
-        * the one we want, fail --- this suggests that there is data we can't
-        * use.
+        * XXX Possibly better way, but much more expensive: multiply by
+        * selectivity of rel's restriction clauses that mention the target
+        * Var.
         */
-       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
-                                                STATISTIC_KIND_HISTOGRAM, sortop,
-                                                &values, &nvalues,
-                                                NULL, NULL))
-       {
-               if (nvalues > 0)
-               {
-                       tmax = datumCopy(values[nvalues - 1], typByVal, typLen);
-                       have_max = true;
-               }
-               free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
-       }
+       if (vardata.rel)
+               ndistinct *= vardata.rel->rows / vardata.rel->tuples;
+
+       /*
+        * Initial estimate of bucketsize fraction is 1/nbuckets as long as
+        * the number of buckets is less than the expected number of distinct
+        * values; otherwise it is 1/ndistinct.
+        */
+       if (ndistinct > (double) nbuckets)
+               estfract = 1.0 / (double) nbuckets;
        else
-       {
-               Oid                     rsortop = get_commutator(sortop);
+               estfract = 1.0 / ndistinct;
 
-               if (OidIsValid(rsortop) &&
-                       get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
-                                                        STATISTIC_KIND_HISTOGRAM, rsortop,
-                                                        &values, &nvalues,
-                                                        NULL, NULL))
-               {
-                       if (nvalues > 0)
-                       {
-                               tmax = datumCopy(values[0], typByVal, typLen);
-                               have_max = true;
-                       }
-                       free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
-               }
-               else if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
-                                                                 STATISTIC_KIND_HISTOGRAM, InvalidOid,
-                                                                 &values, &nvalues,
-                                                                 NULL, NULL))
+       /*
+        * Look up the frequency of the most common value, if available.
+        */
+       mcvfreq = 0.0;
+
+       if (HeapTupleIsValid(vardata.statsTuple))
+       {
+               if (get_attstatsslot(vardata.statsTuple,
+                                                        vardata.atttype, vardata.atttypmod,
+                                                        STATISTIC_KIND_MCV, InvalidOid,
+                                                        NULL, NULL, &numbers, &nnumbers))
                {
-                       free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
-                       ReleaseSysCache(statsTuple);
-                       return false;
+                       /*
+                        * The first MCV stat is for the most common value.
+                        */
+                       if (nnumbers > 0)
+                               mcvfreq = numbers[0];
+                       free_attstatsslot(vardata.atttype, NULL, 0,
+                                                         numbers, nnumbers);
                }
        }
 
        /*
-        * If we have most-common-values info, look for a large MCV.  This is
-        * needed even if we also have a histogram, since the histogram
-        * excludes the MCVs.  However, usually the MCVs will not be the
-        * extreme values, so avoid unnecessary data copying.
+        * Adjust estimated bucketsize upward to account for skewed
+        * distribution.
         */
-       if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
-                                                STATISTIC_KIND_MCV, InvalidOid,
-                                                &values, &nvalues,
-                                                NULL, NULL))
-       {
-               bool            large_mcv = false;
-               FmgrInfo        opproc;
-
-               fmgr_info(get_opcode(sortop), &opproc);
+       if (avgfreq > 0.0 && mcvfreq > avgfreq)
+               estfract *= mcvfreq / avgfreq;
 
-               for (i = 0; i < nvalues; i++)
-               {
-                       if (!have_max)
-                       {
-                               tmax = values[i];
-                               large_mcv = have_max = true;
-                       }
-                       else if (DatumGetBool(FunctionCall2(&opproc, tmax, values[i])))
-                       {
-                               tmax = values[i];
-                               large_mcv = true;
-                       }
-               }
-               if (large_mcv)
-                       tmax = datumCopy(tmax, typByVal, typLen);
-               free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
-       }
+       /*
+        * Clamp bucketsize to sane range (the above adjustment could easily
+        * produce an out-of-range result).  We set the lower bound a little
+        * above zero, since zero isn't a very sane result.
+        */
+       if (estfract < 1.0e-6)
+               estfract = 1.0e-6;
+       else if (estfract > 1.0)
+               estfract = 1.0;
 
-       ReleaseSysCache(statsTuple);
+       ReleaseVariableStats(vardata);
 
-       *max = tmax;
-       return have_max;
+       return (Selectivity) estfract;
 }
 
+
+/*-------------------------------------------------------------------------
+ *
+ * Support routines
+ *
+ *-------------------------------------------------------------------------
+ */
+
 /*
  * convert_to_scalar
  *       Convert non-NULL values of the indicated types to the comparison
@@ -2903,185 +2809,522 @@ convert_timevalue_to_scalar(Datum value, Oid typid)
 
 
 /*
- * get_att_numdistinct
- *       Estimate the number of distinct values of an attribute.
+ * get_restriction_variable
+ *             Examine the args of a restriction clause to see if it's of the
+ *             form (variable op pseudoconstant) or (pseudoconstant op variable),
+ *             where "variable" could be either a Var or an expression in vars of a
+ *             single relation.  If so, extract information about the variable,
+ *             and also indicate which side it was on and the other argument.
  *
- * var: identifies the attribute to examine.
- * stats: pg_statistic tuple for attribute, or NULL if not available.
+ * Inputs:
+ *     root: the Query
+ *     args: clause argument list
+ *     varRelid: see specs for restriction selectivity functions
  *
- * NB: be careful to produce an integral result, since callers may compare
- * the result to exact integer counts.
+ * Outputs: (these are valid only if TRUE is returned)
+ *     *vardata: gets information about variable (see examine_variable)
+ *     *other: gets other clause argument, stripped of binary relabeling
+ *     *varonleft: set TRUE if variable is on the left, FALSE if on the right
+ *
+ * Returns TRUE if a variable is identified, otherwise FALSE.
+ *
+ * Note: if there are Vars on both sides of the clause, we must fail, because
+ * callers are expecting that the other side will act like a pseudoconstant.
  */
-static double
-get_att_numdistinct(Query *root, Var *var, Form_pg_statistic stats)
+static bool
+get_restriction_variable(Query *root, List *args, int varRelid,
+                                                VariableStatData *vardata, Node **other,
+                                                bool *varonleft)
 {
-       RelOptInfo *rel;
-       double          ntuples;
-
-       /*
-        * Special-case boolean columns: presumably, two distinct values.
-        *
-        * Are there any other cases we should wire in special estimates for?
-        */
-       if (var->vartype == BOOLOID)
-               return 2.0;
+       Node       *left,
+                          *right;
+       VariableStatData rdata;
 
-       /*
-        * Otherwise we need to get the relation size.
-        */
-       rel = find_base_rel(root, var->varno);
-       ntuples = rel->tuples;
+       /* Fail if not a binary opclause (probably shouldn't happen) */
+       if (length(args) != 2)
+               return false;
 
-       if (ntuples <= 0.0)
-               return DEFAULT_NUM_DISTINCT;    /* no data available; return a
-                                                                                * default */
+       left = (Node *) lfirst(args);
+       right = (Node *) lsecond(args);
 
        /*
-        * Look to see if there is a unique index on the attribute. If so, we
-        * assume it's distinct, ignoring pg_statistic info which could be out
-        * of date.
+        * Examine both sides.  Note that when varRelid is nonzero, Vars of
+        * other relations will be treated as pseudoconstants.
         */
-       if (has_unique_index(rel, var->varattno))
-               return ntuples;
+       examine_variable(root, left, varRelid, vardata);
+       examine_variable(root, right, varRelid, &rdata);
 
        /*
-        * If ANALYZE determined a fixed or scaled estimate, use it.
+        * If one side is a variable and the other not, we win.
         */
-       if (stats)
+       if (vardata->rel && rdata.rel == NULL)
        {
-               if (stats->stadistinct > 0.0)
-                       return stats->stadistinct;
-               if (stats->stadistinct < 0.0)
-                       return floor((-stats->stadistinct * ntuples) + 0.5);
+               *varonleft = true;
+               *other = rdata.var;
+               /* Assume we need no ReleaseVariableStats(rdata) here */
+               return true;
        }
 
-       /*
-        * ANALYZE does not compute stats for system attributes, but some of
-        * them can reasonably be assumed unique anyway.
-        */
-       switch (var->varattno)
+       if (vardata->rel == NULL && rdata.rel)
        {
-               case ObjectIdAttributeNumber:
-               case SelfItemPointerAttributeNumber:
-                       return ntuples;
-               case TableOidAttributeNumber:
-                       return 1.0;
+               *varonleft = false;
+               *other = vardata->var;
+               /* Assume we need no ReleaseVariableStats(*vardata) here */
+               *vardata = rdata;
+               return true;
        }
 
-       /*
-        * Estimate ndistinct = ntuples if the table is small, else use
-        * default.
-        */
-       if (ntuples < DEFAULT_NUM_DISTINCT)
-               return ntuples;
+       /* Ooops, clause has wrong structure (probably var op var) */
+       ReleaseVariableStats(*vardata);
+       ReleaseVariableStats(rdata);
 
-       return DEFAULT_NUM_DISTINCT;
+       return false;
 }
 
 /*
- * get_restriction_var
- *             Examine the args of a restriction clause to see if it's of the
- *             form (var op something) or (something op var).  If so, extract
- *             and return the var and the other argument.
- *
- * Inputs:
- *     args: clause argument list
- *     varRelid: see specs for restriction selectivity functions
- *
- * Outputs: (these are set only if TRUE is returned)
- *     *var: gets Var node
- *     *other: gets other clause argument
- *     *varonleft: set TRUE if var is on the left, FALSE if on the right
- *
- * Returns TRUE if a Var is identified, otherwise FALSE.
+ * get_join_variables
+ *             Apply examine_variable() to each side of a join clause.
  */
-static bool
-get_restriction_var(List *args,
-                                       int varRelid,
-                                       Var **var,
-                                       Node **other,
-                                       bool *varonleft)
+static void
+get_join_variables(Query *root, List *args,
+                                  VariableStatData *vardata1, VariableStatData *vardata2)
 {
        Node       *left,
                           *right;
 
        if (length(args) != 2)
-               return false;
+               elog(ERROR, "join operator should take two arguments");
 
        left = (Node *) lfirst(args);
        right = (Node *) lsecond(args);
 
+       examine_variable(root, left, 0, vardata1);
+       examine_variable(root, right, 0, vardata2);
+}
+
+/*
+ * examine_variable
+ *             Try to look up statistical data about an expression.
+ *             Fill in a VariableStatData struct to describe the expression.
+ *
+ * Inputs:
+ *     root: the Query
+ *     node: the expression tree to examine
+ *     varRelid: see specs for restriction selectivity functions
+ *
+ * Outputs: *vardata is filled as follows:
+ *     var: the input expression (with any binary relabeling stripped)
+ *     rel: RelOptInfo for relation containing variable; NULL if expression
+ *             contains no Vars (NOTE this could point to a RelOptInfo of a
+ *             subquery, not one in the current query).
+ *     statsTuple: the pg_statistic entry for the variable, if one exists;
+ *             otherwise NULL.
+ *     atttype, atttypmod: type data to pass to get_attstatsslot().  This is
+ *             commonly the same as the exposed type of the variable argument,
+ *             but can be different in binary-compatible-type cases.
+ *
+ * Caller is responsible for doing ReleaseVariableStats() before exiting.
+ */
+static void
+examine_variable(Query *root, Node *node, int varRelid,
+                                VariableStatData *vardata)
+{
+       Relids          varnos;
+       RelOptInfo *onerel;
+
+       /* Make sure we don't return dangling pointers in vardata */
+       MemSet(vardata, 0, sizeof(VariableStatData));
+
        /* Ignore any binary-compatible relabeling */
 
-       if (IsA(left, RelabelType))
-               left = (Node *) ((RelabelType *) left)->arg;
-       if (IsA(right, RelabelType))
-               right = (Node *) ((RelabelType *) right)->arg;
+       if (IsA(node, RelabelType))
+               node = (Node *) ((RelabelType *) node)->arg;
 
-       /* Look for the var */
+       vardata->var = node;
 
-       if (IsA(left, Var) &&
-               (varRelid == 0 || varRelid == ((Var *) left)->varno))
+       /* Fast path for a simple Var */
+
+       if (IsA(node, Var) &&
+               (varRelid == 0 || varRelid == ((Var *) node)->varno))
        {
-               *var = (Var *) left;
-               *other = right;
-               *varonleft = true;
+               Var                *var = (Var *) node;
+               Oid                     relid;
+
+               vardata->rel = find_base_rel(root, var->varno);
+               vardata->atttype = var->vartype;
+               vardata->atttypmod = var->vartypmod;
+
+               relid = getrelid(var->varno, root->rtable);
+
+               if (OidIsValid(relid))
+               {
+                       vardata->statsTuple = SearchSysCache(STATRELATT,
+                                                                                                ObjectIdGetDatum(relid),
+                                                                                                Int16GetDatum(var->varattno),
+                                                                                                0, 0);
+               }
+               else
+               {
+                       /*
+                        * XXX This means the Var comes from a JOIN or sub-SELECT.  Later
+                        * add code to dig down into the join etc and see if we can trace
+                        * the variable to something with stats.  (But beware of
+                        * sub-SELECTs with DISTINCT/GROUP BY/etc.  Perhaps there are
+                        * no cases where this would really be useful, because we'd have
+                        * flattened the subselect if it is??)
+                        */
+               }
+
+               return;
        }
-       else if (IsA(right, Var) &&
-                        (varRelid == 0 || varRelid == ((Var *) right)->varno))
+
+       /*
+        * Okay, it's a more complicated expression.  Determine variable
+        * membership.  Note that when varRelid isn't zero, only vars of
+        * that relation are considered "real" vars.
+        */
+       varnos = pull_varnos(node);
+
+       onerel = NULL;
+
+       switch (bms_membership(varnos))
        {
-               *var = (Var *) right;
-               *other = left;
-               *varonleft = false;
+               case BMS_EMPTY_SET:
+                       /* No Vars at all ... must be pseudo-constant clause */
+                       break;
+               case BMS_SINGLETON:
+                       if (varRelid == 0 || bms_is_member(varRelid, varnos))
+                       {
+                               onerel = find_base_rel(root,
+                                                (varRelid ? varRelid : bms_singleton_member(varnos)));
+                               vardata->rel = onerel;
+                       }
+                       /* else treat it as a constant */
+                       break;
+               case BMS_MULTIPLE:
+                       if (varRelid == 0)
+                       {
+                               /* treat it as a variable of a join relation */
+                               vardata->rel = find_join_rel(root, varnos);
+                       }
+                       else if (bms_is_member(varRelid, varnos))
+                       {
+                               /* ignore the vars belonging to other relations */
+                               vardata->rel = find_base_rel(root, varRelid);
+                               /* note: no point in expressional-index search here */
+                       }
+                       /* else treat it as a constant */
+                       break;
+       }
+
+       bms_free(varnos);
+
+       vardata->atttype = exprType(node);
+       vardata->atttypmod = exprTypmod(node);
+
+       if (onerel)
+       {
+               /*
+                * We have an expression in vars of a single relation.  Try to
+                * match it to expressional index columns, in hopes of finding
+                * some statistics.
+                *
+                * XXX it's conceivable that there are multiple matches with
+                * different index opclasses; if so, we need to pick one that
+                * matches the operator we are estimating for.  FIXME later.
+                */
+               List       *ilist;
+
+               foreach(ilist, onerel->indexlist)
+               {
+                       IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
+                       List       *indexprs;
+                       int                     pos;
+
+                       indexprs = index->indexprs;
+                       if (indexprs == NIL)
+                               continue;               /* no expressions here... */
+
+                       /*
+                        * Ignore partial indexes since they probably don't reflect
+                        * whole-relation statistics.  Possibly reconsider this later.
+                        */
+                       if (index->indpred)
+                               continue;
+
+                       for (pos = 0; pos < index->ncolumns; pos++)
+                       {
+                               if (index->indexkeys[pos] == 0)
+                               {
+                                       Node       *indexkey;
+
+                                       if (indexprs == NIL)
+                                               elog(ERROR, "too few entries in indexprs list");
+                                       indexkey = (Node *) lfirst(indexprs);
+                                       if (indexkey && IsA(indexkey, RelabelType))
+                                               indexkey = (Node *) ((RelabelType *) indexkey)->arg;
+                                       if (equal(node, indexkey))
+                                       {
+                                               /*
+                                                * Found a match ... is it a unique index?
+                                                * Tests here should match has_unique_index().
+                                                */
+                                               if (index->unique &&
+                                                       index->ncolumns == 1 &&
+                                                       index->indpred == NIL)
+                                                       vardata->isunique = true;
+                                               /* Has it got stats? */
+                                               vardata->statsTuple = SearchSysCache(STATRELATT,
+                                                                                                                        ObjectIdGetDatum(index->indexoid),
+                                                                                                                        Int16GetDatum(pos + 1),
+                                                                                                                        0, 0);
+                                               if (vardata->statsTuple)
+                                                       break;
+                                       }
+                                       indexprs = lnext(indexprs);
+                               }
+                       }
+                       if (vardata->statsTuple)
+                               break;
+               }
+       }
+}
+
+/*
+ * get_variable_numdistinct
+ *       Estimate the number of distinct values of a variable.
+ *
+ * vardata: results of examine_variable
+ *
+ * NB: be careful to produce an integral result, since callers may compare
+ * the result to exact integer counts.
+ */
+static double
+get_variable_numdistinct(VariableStatData *vardata)
+{
+       double          stadistinct;
+       double          ntuples;
+
+       /*
+        * Determine the stadistinct value to use.  There are cases where
+        * we can get an estimate even without a pg_statistic entry, or
+        * can get a better value than is in pg_statistic.
+        */
+       if (HeapTupleIsValid(vardata->statsTuple))
+       {
+               /* Use the pg_statistic entry */
+               Form_pg_statistic stats;
+
+               stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+               stadistinct = stats->stadistinct;
+       }
+       else if (vardata->atttype == BOOLOID)
+       {
+               /*
+                * Special-case boolean columns: presumably, two distinct values.
+                *
+                * Are there any other datatypes we should wire in special
+                * estimates for?
+                */
+               stadistinct = 2.0;
        }
        else
        {
-               /* Duh, it's too complicated for me... */
-               return false;
+               /*
+                * We don't keep statistics for system columns, but in some
+                * cases we can infer distinctness anyway.
+                */
+               if (vardata->var && IsA(vardata->var, Var))
+               {
+                       switch (((Var *) vardata->var)->varattno)
+                       {
+                               case ObjectIdAttributeNumber:
+                               case SelfItemPointerAttributeNumber:
+                                       stadistinct = -1.0;                     /* unique */
+                                       break;
+                               case TableOidAttributeNumber:
+                                       stadistinct = 1.0;                      /* only 1 value */
+                                       break;
+                               default:
+                                       stadistinct = 0.0;                      /* means "unknown" */
+                                       break;
+                       }
+               }
+               else
+                       stadistinct = 0.0;                                      /* means "unknown" */
+               /*
+                * XXX consider using estimate_num_groups on expressions?
+                */
+       }
+
+       /*
+        * If there is a unique index for the variable, assume it is unique
+        * no matter what pg_statistic says (the statistics could be out
+        * of date).  Can skip search if we already think it's unique.
+        */
+       if (stadistinct != -1.0)
+       {
+               if (vardata->isunique)
+                       stadistinct = -1.0;
+               else if (vardata->var && IsA(vardata->var, Var) &&
+                                vardata->rel &&
+                                has_unique_index(vardata->rel, 
+                                                                 ((Var *) vardata->var)->varattno))
+                       stadistinct = -1.0;
        }
 
-       return true;
+       /*
+        * If we had an absolute estimate, use that.
+        */
+       if (stadistinct > 0.0)
+               return stadistinct;
+
+       /*
+        * Otherwise we need to get the relation size; punt if not available.
+        */
+       if (vardata->rel == NULL)
+               return DEFAULT_NUM_DISTINCT;
+       ntuples = vardata->rel->tuples;
+       if (ntuples <= 0.0)
+               return DEFAULT_NUM_DISTINCT;
+
+       /*
+        * If we had a relative estimate, use that.
+        */
+       if (stadistinct < 0.0)
+               return floor((-stadistinct * ntuples) + 0.5);
+
+       /*
+        * With no data, estimate ndistinct = ntuples if the table is small,
+        * else use default.
+        */
+       if (ntuples < DEFAULT_NUM_DISTINCT)
+               return ntuples;
+
+       return DEFAULT_NUM_DISTINCT;
 }
 
 /*
- * get_join_vars
+ * get_variable_maximum
+ *             Estimate the maximum value of the specified variable.
+ *             If successful, store value in *max and return TRUE.
+ *             If no data available, return FALSE.
  *
- * Extract the two Vars from a join clause's argument list.  Returns
- * NULL for arguments that are not simple vars.
+ * sortop is the "<" comparison operator to use.  (To extract the
+ * minimum instead of the maximum, just pass the ">" operator instead.)
  */
-static void
-get_join_vars(List *args, Var **var1, Var **var2)
+static bool
+get_variable_maximum(Query *root, VariableStatData *vardata,
+                                        Oid sortop, Datum *max)
 {
-       Node       *left,
-                          *right;
+       Datum           tmax = 0;
+       bool            have_max = false;
+       Form_pg_statistic stats;
+       int16           typLen;
+       bool            typByVal;
+       Datum      *values;
+       int                     nvalues;
+       int                     i;
 
-       if (length(args) != 2)
+       if (!HeapTupleIsValid(vardata->statsTuple))
        {
-               *var1 = NULL;
-               *var2 = NULL;
-               return;
+               /* no stats available, so default result */
+               return false;
        }
+       stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
 
-       left = (Node *) lfirst(args);
-       right = (Node *) lsecond(args);
+       get_typlenbyval(vardata->atttype, &typLen, &typByVal);
 
-       /* Ignore any binary-compatible relabeling */
-       if (IsA(left, RelabelType))
-               left = (Node *) ((RelabelType *) left)->arg;
-       if (IsA(right, RelabelType))
-               right = (Node *) ((RelabelType *) right)->arg;
-
-       if (IsA(left, Var))
-               *var1 = (Var *) left;
+       /*
+        * If there is a histogram, grab the last or first value as
+        * appropriate.
+        *
+        * If there is a histogram that is sorted with some other operator than
+        * the one we want, fail --- this suggests that there is data we can't
+        * use.
+        */
+       if (get_attstatsslot(vardata->statsTuple,
+                                                vardata->atttype, vardata->atttypmod,
+                                                STATISTIC_KIND_HISTOGRAM, sortop,
+                                                &values, &nvalues,
+                                                NULL, NULL))
+       {
+               if (nvalues > 0)
+               {
+                       tmax = datumCopy(values[nvalues - 1], typByVal, typLen);
+                       have_max = true;
+               }
+               free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
+       }
        else
-               *var1 = NULL;
+       {
+               Oid                     rsortop = get_commutator(sortop);
 
-       if (IsA(right, Var))
-               *var2 = (Var *) right;
-       else
-               *var2 = NULL;
+               if (OidIsValid(rsortop) &&
+                       get_attstatsslot(vardata->statsTuple,
+                                                        vardata->atttype, vardata->atttypmod,
+                                                        STATISTIC_KIND_HISTOGRAM, rsortop,
+                                                        &values, &nvalues,
+                                                        NULL, NULL))
+               {
+                       if (nvalues > 0)
+                       {
+                               tmax = datumCopy(values[0], typByVal, typLen);
+                               have_max = true;
+                       }
+                       free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
+               }
+               else if (get_attstatsslot(vardata->statsTuple,
+                                                                 vardata->atttype, vardata->atttypmod,
+                                                                 STATISTIC_KIND_HISTOGRAM, InvalidOid,
+                                                                 &values, &nvalues,
+                                                                 NULL, NULL))
+               {
+                       free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
+                       return false;
+               }
+       }
+
+       /*
+        * If we have most-common-values info, look for a large MCV.  This is
+        * needed even if we also have a histogram, since the histogram
+        * excludes the MCVs.  However, usually the MCVs will not be the
+        * extreme values, so avoid unnecessary data copying.
+        */
+       if (get_attstatsslot(vardata->statsTuple,
+                                                vardata->atttype, vardata->atttypmod,
+                                                STATISTIC_KIND_MCV, InvalidOid,
+                                                &values, &nvalues,
+                                                NULL, NULL))
+       {
+               bool            large_mcv = false;
+               FmgrInfo        opproc;
+
+               fmgr_info(get_opcode(sortop), &opproc);
+
+               for (i = 0; i < nvalues; i++)
+               {
+                       if (!have_max)
+                       {
+                               tmax = values[i];
+                               large_mcv = have_max = true;
+                       }
+                       else if (DatumGetBool(FunctionCall2(&opproc, tmax, values[i])))
+                       {
+                               tmax = values[i];
+                               large_mcv = true;
+                       }
+               }
+               if (large_mcv)
+                       tmax = datumCopy(tmax, typByVal, typLen);
+               free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
+       }
+
+       *max = tmax;
+       return have_max;
 }
 
+
 /*-------------------------------------------------------------------------
  *
  * Pattern analysis functions
@@ -3387,10 +3630,11 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype,
  * Estimate the selectivity of a fixed prefix for a pattern match.
  *
  * A fixed prefix "foo" is estimated as the selectivity of the expression
- * "var >= 'foo' AND var < 'fop'" (see also indxqual.c).
+ * "variable >= 'foo' AND variable < 'fop'" (see also indxqual.c).
  *
  * We use the >= and < operators from the specified btree opclass to do the
- * estimation. The given Var and Const must be of the associated datatype.
+ * estimation. The given variable and Const must be of the associated
+ * datatype.
  *
  * XXX Note: we make use of the upper bound to estimate operator selectivity
  * even if the locale is such that we cannot rely on the upper-bound string.
@@ -3398,7 +3642,8 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype,
  * more useful to use the upper-bound code than not.
  */
 static Selectivity
-prefix_selectivity(Query *root, Var *var, Oid opclass, Const *prefixcon)
+prefix_selectivity(Query *root, VariableStatData *vardata,
+                                  Oid opclass, Const *prefixcon)
 {
        Selectivity prefixsel;
        Oid                     cmpopr;
@@ -3409,7 +3654,7 @@ prefix_selectivity(Query *root, Var *var, Oid opclass, Const *prefixcon)
                                                                BTGreaterEqualStrategyNumber);
        if (cmpopr == InvalidOid)
                elog(ERROR, "no >= operator for opclass %u", opclass);
-       cmpargs = makeList2(var, prefixcon);
+       cmpargs = makeList2(vardata->var, prefixcon);
        /* Assume scalargtsel is appropriate for all supported types */
        prefixsel = DatumGetFloat8(DirectFunctionCall4(scalargtsel,
                                                                                                   PointerGetDatum(root),
@@ -3431,7 +3676,7 @@ prefix_selectivity(Query *root, Var *var, Oid opclass, Const *prefixcon)
                                                                        BTLessStrategyNumber);
                if (cmpopr == InvalidOid)
                        elog(ERROR, "no < operator for opclass %u", opclass);
-               cmpargs = makeList2(var, greaterstrcon);
+               cmpargs = makeList2(vardata->var, greaterstrcon);
                /* Assume scalarltsel is appropriate for all supported types */
                topsel = DatumGetFloat8(DirectFunctionCall4(scalarltsel,
                                                                                                        PointerGetDatum(root),
@@ -3446,7 +3691,7 @@ prefix_selectivity(Query *root, Var *var, Oid opclass, Const *prefixcon)
                prefixsel = topsel + prefixsel - 1.0;
 
                /* Adjust for double-exclusion of NULLs */
-               prefixsel += nulltestsel(root, IS_NULL, (Node *) var, var->varno);
+               prefixsel += nulltestsel(root, IS_NULL, vardata->var, 0);
 
                /*
                 * A zero or slightly negative prefixsel should be converted into
@@ -4034,56 +4279,69 @@ btcostestimate(PG_FUNCTION_ARGS)
        Cost       *indexTotalCost = (Cost *) PG_GETARG_POINTER(5);
        Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(6);
        double     *indexCorrelation = (double *) PG_GETARG_POINTER(7);
+       Oid                     relid;
+       AttrNumber      colnum;
+       HeapTuple       tuple;
 
        genericcostestimate(root, rel, index, indexQuals,
                                                indexStartupCost, indexTotalCost,
                                                indexSelectivity, indexCorrelation);
 
        /*
-        * If the first column is a simple variable, and we can get an
-        * estimate for its ordering correlation C from pg_statistic, estimate
-        * the index correlation as C / number-of-columns. (The idea here is
+        * If we can get an estimate of the first column's ordering correlation C
+        * from pg_statistic, estimate the index correlation as C for a single-
+        * column index, or C * 0.75 for multiple columns.  (The idea here is
         * that multiple columns dilute the importance of the first column's
-        * ordering, but don't negate it entirely.)
+        * ordering, but don't negate it entirely.  Before 7.5 we divided the
+        * correlation by the number of columns, but that seems too strong.)
         */
        if (index->indexkeys[0] != 0)
        {
-               Oid                     relid;
-               HeapTuple       tuple;
-
+               /* Simple variable --- look to stats for the underlying table */
                relid = getrelid(rel->relid, root->rtable);
                Assert(relid != InvalidOid);
-               tuple = SearchSysCache(STATRELATT,
-                                                          ObjectIdGetDatum(relid),
-                                                          Int16GetDatum(index->indexkeys[0]),
-                                                          0, 0);
-               if (HeapTupleIsValid(tuple))
+               colnum = index->indexkeys[0];
+       }
+       else
+       {
+               /* Expression --- maybe there are stats for the index itself */
+               relid = index->indexoid;
+               colnum = 1;
+       }
+
+       tuple = SearchSysCache(STATRELATT,
+                                                  ObjectIdGetDatum(relid),
+                                                  Int16GetDatum(colnum),
+                                                  0, 0);
+
+       if (HeapTupleIsValid(tuple))
+       {
+               Oid                     typid;
+               int32           typmod;
+               float4     *numbers;
+               int                     nnumbers;
+
+               /* XXX this code would break with different storage type */
+               get_atttypetypmod(relid, colnum, &typid, &typmod);
+
+               if (get_attstatsslot(tuple, typid, typmod,
+                                                        STATISTIC_KIND_CORRELATION,
+                                                        index->ordering[0],
+                                                        NULL, NULL, &numbers, &nnumbers))
                {
-                       Oid                     typid;
-                       int32           typmod;
-                       float4     *numbers;
-                       int                     nnumbers;
-
-                       get_atttypetypmod(relid, index->indexkeys[0],
-                                                         &typid, &typmod);
-                       if (get_attstatsslot(tuple, typid, typmod,
-                                                                STATISTIC_KIND_CORRELATION,
-                                                                index->ordering[0],
-                                                                NULL, NULL, &numbers, &nnumbers))
-                       {
-                               double          varCorrelation;
-                               int                     nKeys;
+                       double          varCorrelation;
 
-                               Assert(nnumbers == 1);
-                               varCorrelation = numbers[0];
-                               nKeys = index->ncolumns;
+                       Assert(nnumbers == 1);
+                       varCorrelation = numbers[0];
 
-                               *indexCorrelation = varCorrelation / nKeys;
+                       if (index->ncolumns > 1)
+                               *indexCorrelation = varCorrelation * 0.75;
+                       else
+                               *indexCorrelation = varCorrelation;
 
-                               free_attstatsslot(typid, NULL, 0, numbers, nnumbers);
-                       }
-                       ReleaseSysCache(tuple);
+                       free_attstatsslot(typid, NULL, 0, numbers, nnumbers);
                }
+               ReleaseSysCache(tuple);
        }
 
        PG_RETURN_VOID();
index 379e2ba7a5e614e3fb38352e7523d4f99a9a71db..3186b8d1c1f6e7aca0f43d265279cb4b1eb65e43 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.53 2003/11/29 22:41:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.54 2004/02/17 00:52:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,6 +77,7 @@ extern HashPath *create_hashjoin_path(Query *root,
 extern void build_base_rel(Query *root, int relid);
 extern RelOptInfo *build_other_rel(Query *root, int relid);
 extern RelOptInfo *find_base_rel(Query *root, int relid);
+extern RelOptInfo *find_join_rel(Query *root, Relids relids);
 extern RelOptInfo *build_join_rel(Query *root,
                           Relids joinrelids,
                           RelOptInfo *outer_rel,
index 873af8b9876bc15b642ebd96a5b231e0a1f9a15d..797e0a4c700cc735f5a9d39c3d9df47b56aea5a0 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.16 2003/11/29 22:41:16 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.17 2004/02/17 00:52:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,6 +77,9 @@ extern void mergejoinscansel(Query *root, Node *clause,
 extern double estimate_num_groups(Query *root, List *groupExprs,
                                        double input_rows);
 
+extern Selectivity estimate_hash_bucketsize(Query *root, Node *hashkey,
+                                                                                       int nbuckets);
+
 extern Datum btcostestimate(PG_FUNCTION_ARGS);
 extern Datum rtcostestimate(PG_FUNCTION_ARGS);
 extern Datum hashcostestimate(PG_FUNCTION_ARGS);