/* Look for default "<" and "=" operators for column's type */
        get_sort_group_operators(stats->attrtypid,
                                                         false, false, false,
-                                                        <opr, &eqopr, NULL);
+                                                        <opr, &eqopr, NULL,
+                                                        NULL);
 
        /* If column has no "=" operator, we can't do much of anything */
        if (!OidIsValid(eqopr))
 
        COPY_SCALAR_FIELD(eqop);
        COPY_SCALAR_FIELD(sortop);
        COPY_SCALAR_FIELD(nulls_first);
+       COPY_SCALAR_FIELD(hashable);
 
        return newnode;
 }
 
        COMPARE_SCALAR_FIELD(eqop);
        COMPARE_SCALAR_FIELD(sortop);
        COMPARE_SCALAR_FIELD(nulls_first);
+       COMPARE_SCALAR_FIELD(hashable);
 
        return true;
 }
 
        WRITE_OID_FIELD(eqop);
        WRITE_OID_FIELD(sortop);
        WRITE_BOOL_FIELD(nulls_first);
+       WRITE_BOOL_FIELD(hashable);
 }
 
 static void
 
        READ_OID_FIELD(eqop);
        READ_OID_FIELD(sortop);
        READ_BOOL_FIELD(nulls_first);
+       READ_BOOL_FIELD(hashable);
 
        READ_DONE();
 }
 
                                        (IsA(inner_em->em_expr, RelabelType) &&
                                         IsA(((RelabelType *) inner_em->em_expr)->arg, Var)))
                                        score++;
-                               if (!enable_hashjoin || op_hashjoinable(eq_op))
+                               if (!enable_hashjoin ||
+                                       op_hashjoinable(eq_op,
+                                                                       exprType((Node *) outer_em->em_expr)))
                                        score++;
                                if (score > best_score)
                                {
 
                        sortcl->eqop = eqop;
                        sortcl->sortop = sortop;
                        sortcl->nulls_first = false;
+                       sortcl->hashable = false;               /* no need to make this accurate */
                        sortList = lappend(sortList, sortcl);
                        groupColPos++;
                }
 
 
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/joininfo.h"
 {
        Expr       *clause = restrictinfo->clause;
        Oid                     opno;
+       Node       *leftarg;
 
        if (restrictinfo->pseudoconstant)
                return;
                return;
 
        opno = ((OpExpr *) clause)->opno;
+       leftarg = linitial(((OpExpr *) clause)->args);
 
-       if (op_mergejoinable(opno) &&
+       if (op_mergejoinable(opno, exprType(leftarg)) &&
                !contain_volatile_functions((Node *) clause))
                restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
 
 {
        Expr       *clause = restrictinfo->clause;
        Oid                     opno;
+       Node       *leftarg;
 
        if (restrictinfo->pseudoconstant)
                return;
                return;
 
        opno = ((OpExpr *) clause)->opno;
+       leftarg = linitial(((OpExpr *) clause)->args);
 
-       if (op_hashjoinable(opno) &&
+       if (op_hashjoinable(opno, exprType(leftarg)) &&
                !contain_volatile_functions((Node *) clause))
                restrictinfo->hashjoinoperator = opno;
 }
 
                         info->aggsortop);
        sortcl->sortop = info->aggsortop;
        sortcl->nulls_first = info->nulls_first;
+       sortcl->hashable = false;                       /* no need to make this accurate */
        subparse->sortClause = list_make1(sortcl);
 
        /* set up LIMIT 1 */
 
        return false;
 }
 
+/*
+ * Check expression is hashable + strict
+ *
+ * We could use op_hashjoinable() and op_strict(), but do it like this to
+ * avoid a redundant cache lookup.
+ */
 static bool
 hash_ok_operator(OpExpr *expr)
 {
        Oid                     opid = expr->opno;
-       HeapTuple       tup;
-       Form_pg_operator optup;
 
        /* quick out if not a binary operator */
        if (list_length(expr->args) != 2)
                return false;
-       /* else must look up the operator properties */
-       tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opid));
-       if (!HeapTupleIsValid(tup))
-               elog(ERROR, "cache lookup failed for operator %u", opid);
-       optup = (Form_pg_operator) GETSTRUCT(tup);
-       if (!optup->oprcanhash || !func_strict(optup->oprcode))
+       if (opid == ARRAY_EQ_OP)
+       {
+               /* array_eq is strict, but must check input type to ensure hashable */
+               Node       *leftarg = linitial(expr->args);
+
+               return op_hashjoinable(opid, exprType(leftarg));
+       }
+       else
        {
+               /* else must look up the operator properties */
+               HeapTuple       tup;
+               Form_pg_operator optup;
+
+               tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opid));
+               if (!HeapTupleIsValid(tup))
+                       elog(ERROR, "cache lookup failed for operator %u", opid);
+               optup = (Form_pg_operator) GETSTRUCT(tup);
+               if (!optup->oprcanhash || !func_strict(optup->oprcode))
+               {
+                       ReleaseSysCache(tup);
+                       return false;
+               }
                ReleaseSysCache(tup);
-               return false;
+               return true;
        }
-       ReleaseSysCache(tup);
-       return true;
 }
 
 
 
 
 #include "catalog/pg_operator.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
                Relids          left_varnos;
                Relids          right_varnos;
                Relids          all_varnos;
+               Oid                     opinputtype;
 
                /* Is it a binary opclause? */
                if (!IsA(op, OpExpr) ||
                left_varnos = pull_varnos(left_expr);
                right_varnos = pull_varnos(right_expr);
                all_varnos = bms_union(left_varnos, right_varnos);
+               opinputtype = exprType(left_expr);
 
                /* Does it reference both sides? */
                if (!bms_overlap(all_varnos, sjinfo->syn_righthand) ||
                if (all_btree)
                {
                        /* oprcanmerge is considered a hint... */
-                       if (!op_mergejoinable(opno) ||
+                       if (!op_mergejoinable(opno, opinputtype) ||
                                get_mergejoin_opfamilies(opno) == NIL)
                                all_btree = false;
                }
                if (all_hash)
                {
                        /* ... but oprcanhash had better be correct */
-                       if (!op_hashjoinable(opno))
+                       if (!op_hashjoinable(opno, opinputtype))
                                all_hash = false;
                }
                if (!(all_btree || all_hash))
 
 #include "nodes/nodeFuncs.h"
 #include "optimizer/tlist.h"
 #include "optimizer/var.h"
-#include "utils/lsyscache.h"
 
 
 /*****************************************************************************
 /*
  * grouping_is_hashable - is it possible to implement grouping list by hashing?
  *
- * We assume hashing is OK if the equality operators are marked oprcanhash.
- * (If there isn't actually a supporting hash function, the executor will
- * complain at runtime; but this is a misdeclaration of the operator, not
- * a system bug.)
+ * We rely on the parser to have set the hashable flag correctly.
  */
 bool
 grouping_is_hashable(List *groupClause)
        {
                SortGroupClause *groupcl = (SortGroupClause *) lfirst(glitem);
 
-               if (!op_hashjoinable(groupcl->eqop))
+               if (!groupcl->hashable)
                        return false;
        }
        return true;
 
                                SortGroupClause *grpcl = makeNode(SortGroupClause);
                                Oid                     sortop;
                                Oid                     eqop;
+                               bool            hashable;
                                ParseCallbackState pcbstate;
 
                                setup_parser_errposition_callback(&pcbstate, pstate,
                                /* determine the eqop and optional sortop */
                                get_sort_group_operators(rescoltype,
                                                                                 false, true, false,
-                                                                                &sortop, &eqop, NULL);
+                                                                                &sortop, &eqop, NULL,
+                                                                                &hashable);
 
                                cancel_parser_errposition_callback(&pcbstate);
 
                                grpcl->eqop = eqop;
                                grpcl->sortop = sortop;
                                grpcl->nulls_first = false;             /* OK with or without sortop */
+                               grpcl->hashable = hashable;
 
                                op->groupClauses = lappend(op->groupClauses, grpcl);
                        }
 
        Oid                     restype = exprType((Node *) tle->expr);
        Oid                     sortop;
        Oid                     eqop;
+       bool            hashable;
        bool            reverse;
        int                     location;
        ParseCallbackState pcbstate;
                case SORTBY_ASC:
                        get_sort_group_operators(restype,
                                                                         true, true, false,
-                                                                        &sortop, &eqop, NULL);
+                                                                        &sortop, &eqop, NULL,
+                                                                        &hashable);
                        reverse = false;
                        break;
                case SORTBY_DESC:
                        get_sort_group_operators(restype,
                                                                         false, true, true,
-                                                                        NULL, &eqop, &sortop);
+                                                                        NULL, &eqop, &sortop,
+                                                                        &hashable);
                        reverse = true;
                        break;
                case SORTBY_USING:
                                           errmsg("operator %s is not a valid ordering operator",
                                                          strVal(llast(sortby->useOp))),
                                                 errhint("Ordering operators must be \"<\" or \">\" members of btree operator families.")));
+
+                       /*
+                        * Also see if the equality operator is hashable.
+                        */
+                       hashable = op_hashjoinable(eqop, restype);
                        break;
                default:
                        elog(ERROR, "unrecognized sortby_dir: %d", sortby->sortby_dir);
                        sortop = InvalidOid;    /* keep compiler quiet */
                        eqop = InvalidOid;
+                       hashable = false;
                        reverse = false;
                        break;
        }
 
                sortcl->eqop = eqop;
                sortcl->sortop = sortop;
+               sortcl->hashable = hashable;
 
                switch (sortby->sortby_nulls)
                {
                                         bool resolveUnknown)
 {
        Oid                     restype = exprType((Node *) tle->expr);
-       Oid                     sortop;
-       Oid                     eqop;
 
        /* if tlist item is an UNKNOWN literal, change it to TEXT */
        if (restype == UNKNOWNOID && resolveUnknown)
        if (!targetIsInSortList(tle, InvalidOid, grouplist))
        {
                SortGroupClause *grpcl = makeNode(SortGroupClause);
+               Oid                     sortop;
+               Oid                     eqop;
+               bool            hashable;
                ParseCallbackState pcbstate;
 
                setup_parser_errposition_callback(&pcbstate, pstate, location);
                /* determine the eqop and optional sortop */
                get_sort_group_operators(restype,
                                                                 false, true, false,
-                                                                &sortop, &eqop, NULL);
+                                                                &sortop, &eqop, NULL,
+                                                                &hashable);
 
                cancel_parser_errposition_callback(&pcbstate);
 
                grpcl->eqop = eqop;
                grpcl->sortop = sortop;
                grpcl->nulls_first = false;             /* OK with or without sortop */
+               grpcl->hashable = hashable;
 
                grouplist = lappend(grouplist, grpcl);
        }
 
  * If an operator is missing and the corresponding needXX flag is true,
  * throw a standard error message, else return InvalidOid.
  *
+ * In addition to the operator OIDs themselves, this function can identify
+ * whether the "=" operator is hashable.
+ *
  * Callers can pass NULL pointers for any results they don't care to get.
  *
  * Note: the results are guaranteed to be exact or binary-compatible matches,
 void
 get_sort_group_operators(Oid argtype,
                                                 bool needLT, bool needEQ, bool needGT,
-                                                Oid *ltOpr, Oid *eqOpr, Oid *gtOpr)
+                                                Oid *ltOpr, Oid *eqOpr, Oid *gtOpr,
+                                                bool *isHashable)
 {
        TypeCacheEntry *typentry;
+       int                     cache_flags;
        Oid                     lt_opr;
        Oid                     eq_opr;
        Oid                     gt_opr;
+       bool            hashable;
 
        /*
         * Look up the operators using the type cache.
         *
         * Note: the search algorithm used by typcache.c ensures that the results
-        * are consistent, ie all from the same opclass.
+        * are consistent, ie all from matching opclasses.
         */
-       typentry = lookup_type_cache(argtype,
-                                        TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR);
+       if (isHashable != NULL)
+               cache_flags = TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR |
+                       TYPECACHE_HASH_PROC;
+       else
+               cache_flags = TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR;
+
+       typentry = lookup_type_cache(argtype, cache_flags);
        lt_opr = typentry->lt_opr;
        eq_opr = typentry->eq_opr;
        gt_opr = typentry->gt_opr;
+       hashable = OidIsValid(typentry->hash_proc);
 
        /*
         * If the datatype is an array, then we can use array_lt and friends ...
-        * but only if there are suitable operators for the element type.  (This
-        * check is not in the raw typcache.c code ... should it be?)  Testing all
-        * three operator IDs here should be redundant, but let's do it anyway.
+        * but only if there are suitable operators for the element type.
+        * Likewise, array types are only hashable if the element type is.
+        * Testing all three operator IDs here should be redundant, but let's do
+        * it anyway.
         */
        if (lt_opr == ARRAY_LT_OP ||
                eq_opr == ARRAY_EQ_OP ||
 
                if (OidIsValid(elem_type))
                {
-                       typentry = lookup_type_cache(elem_type,
-                                        TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR);
-#ifdef NOT_USED
-                       /* We should do this ... */
+                       typentry = lookup_type_cache(elem_type, cache_flags);
                        if (!OidIsValid(typentry->eq_opr))
                        {
                                /* element type is neither sortable nor hashable */
                                /* element type is hashable but not sortable */
                                lt_opr = gt_opr = InvalidOid;
                        }
-#else
-
-                       /*
-                        * ... but for the moment we have to do this.  This is because
-                        * anyarray has sorting but not hashing support.  So, if the
-                        * element type is only hashable, there is nothing we can do with
-                        * the array type.
-                        */
-                       if (!OidIsValid(typentry->lt_opr) ||
-                               !OidIsValid(typentry->eq_opr) ||
-                               !OidIsValid(typentry->gt_opr))
-                               lt_opr = eq_opr = gt_opr = InvalidOid;  /* not sortable */
-#endif
+                       hashable = OidIsValid(typentry->hash_proc);
                }
                else
+               {
                        lt_opr = eq_opr = gt_opr = InvalidOid;          /* bogus array type? */
+                       hashable = false;
+               }
        }
 
        /* Report errors if needed */
                *eqOpr = eq_opr;
        if (gtOpr)
                *gtOpr = gt_opr;
+       if (isHashable)
+               *isHashable = hashable;
 }
 
 
 
 }
 
 
+/*-----------------------------------------------------------------------------
+ * array hashing
+ *             Hash the elements and combine the results.
+ *----------------------------------------------------------------------------
+ */
+
+Datum
+hash_array(PG_FUNCTION_ARGS)
+{
+       ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+       int                     ndims = ARR_NDIM(array);
+       int                *dims = ARR_DIMS(array);
+       Oid                     element_type = ARR_ELEMTYPE(array);
+       uint32          result = 0;
+       int                     nitems;
+       TypeCacheEntry *typentry;
+       int                     typlen;
+       bool            typbyval;
+       char            typalign;
+       char       *ptr;
+       bits8      *bitmap;
+       int                     bitmask;
+       int                     i;
+       FunctionCallInfoData locfcinfo;
+
+       /*
+        * We arrange to look up the hash function only once per series of calls,
+        * assuming the element type doesn't change underneath us.  The typcache
+        * is used so that we have no memory leakage when being used as an index
+        * support function.
+        */
+       typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+       if (typentry == NULL ||
+               typentry->type_id != element_type)
+       {
+               typentry = lookup_type_cache(element_type,
+                                                                        TYPECACHE_HASH_PROC_FINFO);
+               if (!OidIsValid(typentry->hash_proc_finfo.fn_oid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                        errmsg("could not identify a hash function for type %s",
+                                                       format_type_be(element_type))));
+               fcinfo->flinfo->fn_extra = (void *) typentry;
+       }
+       typlen = typentry->typlen;
+       typbyval = typentry->typbyval;
+       typalign = typentry->typalign;
+
+       /*
+        * apply the hash function to each array element.
+        */
+       InitFunctionCallInfoData(locfcinfo, &typentry->hash_proc_finfo, 1,
+                                                        NULL, NULL);
+
+       /* Loop over source data */
+       nitems = ArrayGetNItems(ndims, dims);
+       ptr = ARR_DATA_PTR(array);
+       bitmap = ARR_NULLBITMAP(array);
+       bitmask = 1;
+
+       for (i = 0; i < nitems; i++)
+       {
+               uint32          elthash;
+
+               /* Get element, checking for NULL */
+               if (bitmap && (*bitmap & bitmask) == 0)
+               {
+                       /* Treat nulls as having hashvalue 0 */
+                       elthash = 0;
+               }
+               else
+               {
+                       Datum           elt;
+
+                       elt = fetch_att(ptr, typbyval, typlen);
+                       ptr = att_addlength_pointer(ptr, typlen, ptr);
+                       ptr = (char *) att_align_nominal(ptr, typalign);
+
+                       /* Apply the hash function */
+                       locfcinfo.arg[0] = elt;
+                       locfcinfo.argnull[0] = false;
+                       locfcinfo.isnull = false;
+                       elthash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo));
+               }
+
+               /* advance bitmap pointer if any */
+               if (bitmap)
+               {
+                       bitmask <<= 1;
+                       if (bitmask == 0x100)
+                       {
+                               bitmap++;
+                               bitmask = 1;
+                       }
+               }
+
+               /*
+                * Combine hash values of successive elements by rotating the previous
+                * value left 1 bit, then XOR'ing in the new element's hash value.
+                */
+               result = (result << 1) | (result >> 31);
+               result ^= elthash;
+       }
+
+       /* Avoid leaking memory when handed toasted input. */
+       PG_FREE_IF_COPY(array, 0);
+
+       PG_RETURN_UINT32(result);
+}
+
+
 /*-----------------------------------------------------------------------------
  * array overlap/containment comparisons
  *             These use the same methods of comparing array elements as array_eq.
 
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /* Hook for plugins to get control in get_attavgwidth() */
 get_attavgwidth_hook_type get_attavgwidth_hook = NULL;
  * will fail to find any mergejoin plans unless there are suitable btree
  * opfamily entries for this operator and associated sortops.  The pg_operator
  * flag is just a hint to tell the planner whether to bother looking.)
+ *
+ * In some cases (currently only array_eq), mergejoinability depends on the
+ * specific input data type the operator is invoked for, so that must be
+ * passed as well.  We currently assume that only one input's type is needed
+ * to check this --- by convention, pass the left input's data type.
  */
 bool
-op_mergejoinable(Oid opno)
+op_mergejoinable(Oid opno, Oid inputtype)
 {
        HeapTuple       tp;
        bool            result = false;
 
-       tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
-       if (HeapTupleIsValid(tp))
+       if (opno == ARRAY_EQ_OP)
        {
-               Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
+               /*
+                * For array_eq, can sort if element type has a default btree opclass.
+                * We could use GetDefaultOpClass, but that's fairly expensive and not
+                * cached, so let's use the typcache instead.
+                */
+               Oid                     elem_type = get_base_element_type(inputtype);
 
-               result = optup->oprcanmerge;
-               ReleaseSysCache(tp);
+               if (OidIsValid(elem_type))
+               {
+                       TypeCacheEntry *typentry;
+
+                       typentry = lookup_type_cache(elem_type, TYPECACHE_BTREE_OPFAMILY);
+                       if (OidIsValid(typentry->btree_opf))
+                               result = true;
+               }
+       }
+       else
+       {
+               /* For all other operators, rely on pg_operator.oprcanmerge */
+               tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
+               if (HeapTupleIsValid(tp))
+               {
+                       Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
+
+                       result = optup->oprcanmerge;
+                       ReleaseSysCache(tp);
+               }
        }
        return result;
 }
  *
  * Returns true if the operator is hashjoinable.  (There must be a suitable
  * hash opfamily entry for this operator if it is so marked.)
+ *
+ * In some cases (currently only array_eq), hashjoinability depends on the
+ * specific input data type the operator is invoked for, so that must be
+ * passed as well.  We currently assume that only one input's type is needed
+ * to check this --- by convention, pass the left input's data type.
  */
 bool
-op_hashjoinable(Oid opno)
+op_hashjoinable(Oid opno, Oid inputtype)
 {
        HeapTuple       tp;
        bool            result = false;
 
-       tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
-       if (HeapTupleIsValid(tp))
+       if (opno == ARRAY_EQ_OP)
        {
-               Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
+               /* For array_eq, can hash if element type has a default hash opclass */
+               Oid                     elem_type = get_base_element_type(inputtype);
 
-               result = optup->oprcanhash;
-               ReleaseSysCache(tp);
+               if (OidIsValid(elem_type))
+               {
+                       TypeCacheEntry *typentry;
+
+                       typentry = lookup_type_cache(elem_type, TYPECACHE_HASH_OPFAMILY);
+                       if (OidIsValid(typentry->hash_opf))
+                               result = true;
+               }
+       }
+       else
+       {
+               /* For all other operators, rely on pg_operator.oprcanhash */
+               tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
+               if (HeapTupleIsValid(tp))
+               {
+                       Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
+
+                       result = optup->oprcanhash;
+                       ReleaseSysCache(tp);
+               }
        }
        return result;
 }
 
  * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC).
  *
  * Several seemingly-odd choices have been made to support use of the type
- * cache by the generic array comparison routines array_eq() and array_cmp().
- * Because those routines are used as index support operations, they cannot
- * leak memory.  To allow them to execute efficiently, all information that
- * either of them would like to re-use across calls is made available in the
+ * cache by the generic array handling routines array_eq(), array_cmp(),
+ * and hash_array().  Because those routines are used as index support
+ * operations, they cannot leak memory.  To allow them to execute efficiently,
+ * all information that they would like to re-use across calls is kept in the
  * type cache.
  *
  * Once created, a type cache entry lives as long as the backend does, so
                ReleaseSysCache(tp);
        }
 
-       /* If we haven't already found the opclass, try to do so */
+       /*
+        * If we haven't already found the opclasses, try to do so
+        */
        if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
                                  TYPECACHE_CMP_PROC |
                                  TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
                        typentry->btree_opf = get_opclass_family(opclass);
                        typentry->btree_opintype = get_opclass_input_type(opclass);
                }
-               /* Only care about hash opclass if no btree opclass... */
+               /* If no btree opclass, we force lookup of the hash opclass */
                if (typentry->btree_opf == InvalidOid)
                {
                        if (typentry->hash_opf == InvalidOid)
                else
                {
                        /*
-                        * If we find a btree opclass where previously we only found a
-                        * hash opclass, forget the hash equality operator so we can use
-                        * the btree operator instead.
+                        * In case we find a btree opclass where previously we only found
+                        * a hash opclass, reset eq_opr and derived information so that
+                        * we can fetch the btree equality operator instead of the hash
+                        * equality operator.  (They're probably the same operator, but
+                        * we don't assume that here.)
                         */
                        typentry->eq_opr = InvalidOid;
                        typentry->eq_opr_finfo.fn_oid = InvalidOid;
+                       typentry->hash_proc = InvalidOid;
+                       typentry->hash_proc_finfo.fn_oid = InvalidOid;
+               }
+       }
+
+       if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO |
+                                 TYPECACHE_HASH_OPFAMILY)) &&
+               typentry->hash_opf == InvalidOid)
+       {
+               Oid                     opclass;
+
+               opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
+               if (OidIsValid(opclass))
+               {
+                       typentry->hash_opf = get_opclass_family(opclass);
+                       typentry->hash_opintype = get_opclass_input_type(opclass);
                }
        }
 
                                                                                                   typentry->hash_opintype,
                                                                                                   typentry->hash_opintype,
                                                                                                   HTEqualStrategyNumber);
+
+               /*
+                * Reset info about hash function whenever we pick up new info about
+                * equality operator.  This is so we can ensure that the hash function
+                * matches the operator.
+                */
+               typentry->hash_proc = InvalidOid;
+               typentry->hash_proc_finfo.fn_oid = InvalidOid;
        }
        if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid)
        {
                                                                                                   typentry->btree_opintype,
                                                                                                   BTORDER_PROC);
        }
+       if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)) &&
+               typentry->hash_proc == InvalidOid)
+       {
+               /*
+                * We insist that the eq_opr, if one has been determined, match the
+                * hash opclass; else report there is no hash function.
+                */
+               if (typentry->hash_opf != InvalidOid &&
+                       (!OidIsValid(typentry->eq_opr) ||
+                        typentry->eq_opr == get_opfamily_member(typentry->hash_opf,
+                                                                                                        typentry->hash_opintype,
+                                                                                                        typentry->hash_opintype,
+                                                                                                        HTEqualStrategyNumber)))
+                       typentry->hash_proc = get_opfamily_proc(typentry->hash_opf,
+                                                                                                       typentry->hash_opintype,
+                                                                                                       typentry->hash_opintype,
+                                                                                                       HASHPROC);
+       }
 
        /*
         * Set up fmgr lookup info as requested
                fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
                                          CacheMemoryContext);
        }
+       if ((flags & TYPECACHE_HASH_PROC_FINFO) &&
+               typentry->hash_proc_finfo.fn_oid == InvalidOid &&
+               typentry->hash_proc != InvalidOid)
+       {
+               fmgr_info_cxt(typentry->hash_proc, &typentry->hash_proc_finfo,
+                                         CacheMemoryContext);
+       }
 
        /*
         * If it's a composite type (row type), get tupdesc if requested
 
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201010241
+#define CATALOG_VERSION_NO     201010301
 
 #endif
 
 DATA(insert (  2969   2950 2950 1 2972 405 ));
 /* numeric_ops */
 DATA(insert (  1998   1700 1700 1 1752 405 ));
+/* array_ops */
+DATA(insert (  627    2277 2277 1 1070 405 ));
 
 
 /*
 
 DATA(insert (  427   1042 1042 1 1080 ));
 DATA(insert (  431   18 18 1 454 ));
 DATA(insert (  435   1082 1082 1 450 ));
+DATA(insert (  627   2277 2277 1 626 ));
 DATA(insert (  1971   700 700 1 451 ));
 DATA(insert (  1971   701 701 1 452 ));
 DATA(insert (  1975   869 869 1 422 ));
 
 
 DATA(insert (  403             abstime_ops                     PGNSP PGUID  421  702 t 0 ));
 DATA(insert (  403             array_ops                       PGNSP PGUID  397 2277 t 0 ));
+DATA(insert (  405             array_ops                       PGNSP PGUID  627 2277 t 0 ));
 DATA(insert (  403             bit_ops                         PGNSP PGUID  423 1560 t 0 ));
 DATA(insert (  403             bool_ops                        PGNSP PGUID  424   16 t 0 ));
 DATA(insert (  403             bpchar_ops                      PGNSP PGUID  426 1042 t 0 ));
 
 DATA(insert OID = 1061 ( ">="     PGNSP PGUID b f f 1042 1042   16 1059 1058 bpcharge scalargtsel scalargtjoinsel ));
 
 /* generic array comparison operators */
-DATA(insert OID = 1070 (  "="     PGNSP PGUID b t f 2277 2277 16 1070 1071 array_eq eqsel eqjoinsel ));
+DATA(insert OID = 1070 (  "="     PGNSP PGUID b t t 2277 2277 16 1070 1071 array_eq eqsel eqjoinsel ));
 #define ARRAY_EQ_OP 1070
 DATA(insert OID = 1071 (  "<>"    PGNSP PGUID b f f 2277 2277 16 1071 1070 array_ne neqsel neqjoinsel ));
 DATA(insert OID = 1072 (  "<"     PGNSP PGUID b f f 2277 2277 16 1073 1075 array_lt scalarltsel scalarltjoinsel ));
 
 
 DATA(insert OID =  421 (       403             abstime_ops             PGNSP PGUID ));
 DATA(insert OID =  397 (       403             array_ops               PGNSP PGUID ));
+DATA(insert OID =  627 (       405             array_ops               PGNSP PGUID ));
 DATA(insert OID =  423 (       403             bit_ops                 PGNSP PGUID ));
 DATA(insert OID =  424 (       403             bool_ops                PGNSP PGUID ));
 #define BOOL_BTREE_FAM_OID 424
 
 
 /* OIDS 600 - 699 */
 
+DATA(insert OID = 626 (  hash_array               PGNSP PGUID 12 1 0 0 f f f t f i 1 0 23 "2277" _null_ _null_ _null_ _null_ hash_array _null_ _null_ _null_ ));
+DESCR("hash");
+
 DATA(insert OID = 652 (  float4                           PGNSP PGUID 12 1 0 0 f f f t f i 1 0 700 "20" _null_ _null_ _null_ _null_ i8tof _null_ _null_ _null_ ));
 DESCR("convert int8 to float4");
 DATA(insert OID = 653 (  int8                     PGNSP PGUID 12 1 0 0 f f f t f i 1 0 20 "700" _null_ _null_ _null_ _null_    ftoi8 _null_ _null_ _null_ ));
 
  *             or InvalidOid if not available.
  * nulls_first means about what you'd expect.  If sortop is InvalidOid
  *             then nulls_first is meaningless and should be set to false.
+ * hashable is TRUE if eqop is hashable (note this condition also depends
+ *             on the datatype of the input expression).
  *
  * In an ORDER BY item, all fields must be valid.  (The eqop isn't essential
  * here, but it's cheap to get it along with the sortop, and requiring it
  * and nulls_first to false.  A grouping item of this kind can only be
  * implemented by hashing, and of course it'll never match an ORDER BY item.
  *
+ * The hashable flag is provided since we generally have the requisite
+ * information readily available when the SortGroupClause is constructed,
+ * and it's relatively expensive to get it again later.  Note there is no
+ * need for a "sortable" flag since OidIsValid(sortop) serves the purpose.
+ *
  * A query might have both ORDER BY and DISTINCT (or DISTINCT ON) clauses.
  * In SELECT DISTINCT, the distinctClause list is as long or longer than the
  * sortClause list, while in SELECT DISTINCT ON it's typically shorter.
        Oid                     eqop;                   /* the equality operator ('=' op) */
        Oid                     sortop;                 /* the ordering operator ('<' op), or 0 */
        bool            nulls_first;    /* do NULLs come before normal values? */
+       bool            hashable;               /* can eqop be implemented by hashing? */
 } SortGroupClause;
 
 /*
 
 /* Routines for identifying "<", "=", ">" operators for a type */
 extern void get_sort_group_operators(Oid argtype,
                                                 bool needLT, bool needEQ, bool needGT,
-                                                Oid *ltOpr, Oid *eqOpr, Oid *gtOpr);
+                                                Oid *ltOpr, Oid *eqOpr, Oid *gtOpr,
+                                                bool *isHashable);
 
 /* Convenience routines for common calls on the above */
 extern Oid     compatible_oper_opid(List *op, Oid arg1, Oid arg2, bool noError);
 
 extern Datum array_le(PG_FUNCTION_ARGS);
 extern Datum array_ge(PG_FUNCTION_ARGS);
 extern Datum btarraycmp(PG_FUNCTION_ARGS);
+extern Datum hash_array(PG_FUNCTION_ARGS);
 extern Datum arrayoverlap(PG_FUNCTION_ARGS);
 extern Datum arraycontains(PG_FUNCTION_ARGS);
 extern Datum arraycontained(PG_FUNCTION_ARGS);
 
 extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
 extern void op_input_types(Oid opno, Oid *lefttype, Oid *righttype);
-extern bool op_mergejoinable(Oid opno);
-extern bool op_hashjoinable(Oid opno);
+extern bool op_mergejoinable(Oid opno, Oid inputtype);
+extern bool op_hashjoinable(Oid opno, Oid inputtype);
 extern bool op_strict(Oid opno);
 extern char op_volatile(Oid opno);
 extern Oid     get_commutator(Oid opno);
 
        Oid                     lt_opr;                 /* the less-than operator */
        Oid                     gt_opr;                 /* the greater-than operator */
        Oid                     cmp_proc;               /* the btree comparison function */
+       Oid                     hash_proc;              /* the hash calculation function */
 
        /*
-        * Pre-set-up fmgr call info for the equality operator and the btree
-        * comparison function.  These are kept in the type cache to avoid
-        * problems with memory leaks in repeated calls to array_eq and array_cmp.
-        * There is not currently a need to maintain call info for the lt_opr or
-        * gt_opr.
+        * Pre-set-up fmgr call info for the equality operator, the btree
+        * comparison function, and the hash calculation function.  These are kept
+        * in the type cache to avoid problems with memory leaks in repeated calls
+        * to array_eq, array_cmp, hash_array.  There is not currently a need to
+        * maintain call info for the lt_opr or gt_opr.
         */
        FmgrInfo        eq_opr_finfo;
        FmgrInfo        cmp_proc_finfo;
+       FmgrInfo        hash_proc_finfo;
 
        /*
         * Tuple descriptor if it's a composite type (row type).  NULL if not
 #define TYPECACHE_LT_OPR                       0x0002
 #define TYPECACHE_GT_OPR                       0x0004
 #define TYPECACHE_CMP_PROC                     0x0008
-#define TYPECACHE_EQ_OPR_FINFO         0x0010
-#define TYPECACHE_CMP_PROC_FINFO       0x0020
-#define TYPECACHE_TUPDESC                      0x0040
-#define TYPECACHE_BTREE_OPFAMILY       0x0080
+#define TYPECACHE_HASH_PROC                    0x0010
+#define TYPECACHE_EQ_OPR_FINFO         0x0020
+#define TYPECACHE_CMP_PROC_FINFO       0x0040
+#define TYPECACHE_HASH_PROC_FINFO      0x0080
+#define TYPECACHE_TUPDESC                      0x0100
+#define TYPECACHE_BTREE_OPFAMILY       0x0200
+#define TYPECACHE_HASH_OPFAMILY                0x0400
 
 extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags);