Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions

parse_oper.c

/*-------------------------------------------------------------------------
 *
 * parse_oper.c
 *          handle operator things for parser
 *
 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.109 2009/06/13 15:42:09 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


/*
 * The lookup key for the operator lookaside hash table.  Unused bits must be
 * zeroes to ensure hashing works consistently --- in particular, oprname
 * must be zero-padded and any unused entries in search_path must be zero.
 *
 * search_path contains the actual search_path with which the entry was
 * derived (minus temp namespace if any), or else the single specified
 * schema OID if we are looking up an explicitly-qualified operator name.
 *
 * search_path has to be fixed-length since the hashtable code insists on
 * fixed-size keys.  If your search path is longer than that, we just punt
 * and don't cache anything.
 */

/* If your search_path is longer than this, sucks to be you ... */
#define MAX_CACHED_PATH_LEN         16

typedef struct OprCacheKey
{
      char        oprname[NAMEDATALEN];
      Oid               left_arg;         /* Left input OID, or 0 if prefix op */
      Oid               right_arg;        /* Right input OID, or 0 if postfix op */
      Oid               search_path[MAX_CACHED_PATH_LEN];
} OprCacheKey;

typedef struct OprCacheEntry
{
      /* the hash lookup key MUST BE FIRST */
      OprCacheKey key;

      Oid               opr_oid;          /* OID of the resolved operator */
} OprCacheEntry;


static Oid  binary_oper_exact(List *opname, Oid arg1, Oid arg2);
static FuncDetailCode oper_select_candidate(int nargs,
                                Oid *input_typeids,
                                FuncCandidateList candidates,
                                Oid *operOid);
static const char *op_signature_string(List *op, char oprkind,
                              Oid arg1, Oid arg2);
static void op_error(ParseState *pstate, List *op, char oprkind,
             Oid arg1, Oid arg2,
             FuncDetailCode fdresult, int location);
static bool make_oper_cache_key(OprCacheKey *key, List *opname,
                              Oid ltypeId, Oid rtypeId);
static Oid  find_oper_cache_entry(OprCacheKey *key);
static void make_oper_cache_entry(OprCacheKey *key, Oid opr_oid);
static void InvalidateOprCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr);


/*
 * LookupOperName
 *          Given a possibly-qualified operator name and exact input datatypes,
 *          look up the operator.
 *
 * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for
 * a postfix op.
 *
 * If the operator name is not schema-qualified, it is sought in the current
 * namespace search path.
 *
 * If the operator is not found, we return InvalidOid if noError is true,
 * else raise an error.  pstate and location are used only to report the
 * error position; pass NULL/-1 if not available.
 */
Oid
LookupOperName(ParseState *pstate, List *opername, Oid oprleft, Oid oprright,
                     bool noError, int location)
{
      Oid               result;

      result = OpernameGetOprid(opername, oprleft, oprright);
      if (OidIsValid(result))
            return result;

      /* we don't use op_error here because only an exact match is wanted */
      if (!noError)
      {
            char        oprkind;

            if (!OidIsValid(oprleft))
                  oprkind = 'l';
            else if (!OidIsValid(oprright))
                  oprkind = 'r';
            else
                  oprkind = 'b';

            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("operator does not exist: %s",
                                    op_signature_string(opername, oprkind,
                                                                  oprleft, oprright)),
                         parser_errposition(pstate, location)));
      }

      return InvalidOid;
}

/*
 * LookupOperNameTypeNames
 *          Like LookupOperName, but the argument types are specified by
 *          TypeName nodes.
 *
 * Pass oprleft = NULL for a prefix op, oprright = NULL for a postfix op.
 */
Oid
LookupOperNameTypeNames(ParseState *pstate, List *opername,
                                    TypeName *oprleft, TypeName *oprright,
                                    bool noError, int location)
{
      Oid               leftoid,
                        rightoid;

      if (oprleft == NULL)
            leftoid = InvalidOid;
      else
            leftoid = typenameTypeId(pstate, oprleft, NULL);

      if (oprright == NULL)
            rightoid = InvalidOid;
      else
            rightoid = typenameTypeId(pstate, oprright, NULL);

      return LookupOperName(pstate, opername, leftoid, rightoid,
                                      noError, location);
}

/*
 * get_sort_group_operators - get default sorting/grouping operators for type
 *
 * We fetch the "<", "=", and ">" operators all at once to reduce lookup
 * overhead (knowing that most callers will be interested in at least two).
 * However, a given datatype might have only an "=" operator, if it is
 * hashable but not sortable.  (Other combinations of present and missing
 * operators shouldn't happen, unless the system catalogs are messed up.)
 *
 * If an operator is missing and the corresponding needXX flag is true,
 * throw a standard error message, else return InvalidOid.
 *
 * 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,
 * since most callers are not prepared to cope with adding any run-time type
 * coercion steps.
 */
void
get_sort_group_operators(Oid argtype,
                                     bool needLT, bool needEQ, bool needGT,
                                     Oid *ltOpr, Oid *eqOpr, Oid *gtOpr)
{
      TypeCacheEntry *typentry;
      Oid               lt_opr;
      Oid               eq_opr;
      Oid               gt_opr;

      /*
       * 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.
       */
      typentry = lookup_type_cache(argtype,
                               TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR);
      lt_opr = typentry->lt_opr;
      eq_opr = typentry->eq_opr;
      gt_opr = typentry->gt_opr;

      /*
       * 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.
       */
      if (lt_opr == ARRAY_LT_OP ||
            eq_opr == ARRAY_EQ_OP ||
            gt_opr == ARRAY_GT_OP)
      {
            Oid               elem_type = get_element_type(argtype);

            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 ... */
                  if (!OidIsValid(typentry->eq_opr))
                  {
                        /* element type is neither sortable nor hashable */
                        lt_opr = eq_opr = gt_opr = InvalidOid;
                  }
                  else if (!OidIsValid(typentry->lt_opr) ||
                               !OidIsValid(typentry->gt_opr))
                  {
                        /* 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
            }
            else
                  lt_opr = eq_opr = gt_opr = InvalidOid;          /* bogus array type? */
      }

      /* Report errors if needed */
      if ((needLT && !OidIsValid(lt_opr)) ||
            (needGT && !OidIsValid(gt_opr)))
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("could not identify an ordering operator for type %s",
                                    format_type_be(argtype)),
             errhint("Use an explicit ordering operator or modify the query.")));
      if (needEQ && !OidIsValid(eq_opr))
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("could not identify an equality operator for type %s",
                                    format_type_be(argtype))));

      /* Return results as needed */
      if (ltOpr)
            *ltOpr = lt_opr;
      if (eqOpr)
            *eqOpr = eq_opr;
      if (gtOpr)
            *gtOpr = gt_opr;
}


/* given operator tuple, return the operator OID */
Oid
oprid(Operator op)
{
      return HeapTupleGetOid(op);
}

/* given operator tuple, return the underlying function's OID */
Oid
oprfuncid(Operator op)
{
      Form_pg_operator pgopform = (Form_pg_operator) GETSTRUCT(op);

      return pgopform->oprcode;
}


/* binary_oper_exact()
 * Check for an "exact" match to the specified operand types.
 *
 * If one operand is an unknown literal, assume it should be taken to be
 * the same type as the other operand for this purpose.  Also, consider
 * the possibility that the other operand is a domain type that needs to
 * be reduced to its base type to find an "exact" match.
 */
static Oid
binary_oper_exact(List *opname, Oid arg1, Oid arg2)
{
      Oid               result;
      bool        was_unknown = false;

      /* Unspecified type for one of the arguments? then use the other */
      if ((arg1 == UNKNOWNOID) && (arg2 != InvalidOid))
      {
            arg1 = arg2;
            was_unknown = true;
      }
      else if ((arg2 == UNKNOWNOID) && (arg1 != InvalidOid))
      {
            arg2 = arg1;
            was_unknown = true;
      }

      result = OpernameGetOprid(opname, arg1, arg2);
      if (OidIsValid(result))
            return result;

      if (was_unknown)
      {
            /* arg1 and arg2 are the same here, need only look at arg1 */
            Oid               basetype = getBaseType(arg1);

            if (basetype != arg1)
            {
                  result = OpernameGetOprid(opname, basetype, basetype);
                  if (OidIsValid(result))
                        return result;
            }
      }

      return InvalidOid;
}


/* oper_select_candidate()
 *          Given the input argtype array and one or more candidates
 *          for the operator, attempt to resolve the conflict.
 *
 * Returns FUNCDETAIL_NOTFOUND, FUNCDETAIL_MULTIPLE, or FUNCDETAIL_NORMAL.
 * In the success case the Oid of the best candidate is stored in *operOid.
 *
 * Note that the caller has already determined that there is no candidate
 * exactly matching the input argtype(s).  Incompatible candidates are not yet
 * pruned away, however.
 */
static FuncDetailCode
oper_select_candidate(int nargs,
                                Oid *input_typeids,
                                FuncCandidateList candidates,
                                Oid *operOid)         /* output argument */
{
      int               ncandidates;

      /*
       * Delete any candidates that cannot actually accept the given input
       * types, whether directly or by coercion.
       */
      ncandidates = func_match_argtypes(nargs, input_typeids,
                                                        candidates, &candidates);

      /* Done if no candidate or only one candidate survives */
      if (ncandidates == 0)
      {
            *operOid = InvalidOid;
            return FUNCDETAIL_NOTFOUND;
      }
      if (ncandidates == 1)
      {
            *operOid = candidates->oid;
            return FUNCDETAIL_NORMAL;
      }

      /*
       * Use the same heuristics as for ambiguous functions to resolve the
       * conflict.
       */
      candidates = func_select_candidate(nargs, input_typeids, candidates);

      if (candidates)
      {
            *operOid = candidates->oid;
            return FUNCDETAIL_NORMAL;
      }

      *operOid = InvalidOid;
      return FUNCDETAIL_MULTIPLE; /* failed to select a best candidate */
}


/* oper() -- search for a binary operator
 * Given operator name, types of arg1 and arg2, return oper struct.
 *
 * IMPORTANT: the returned operator (if any) is only promised to be
 * coercion-compatible with the input datatypes.  Do not use this if
 * you need an exact- or binary-compatible match; see compatible_oper.
 *
 * If no matching operator found, return NULL if noError is true,
 * raise an error if it is false.  pstate and location are used only to report
 * the error position; pass NULL/-1 if not available.
 *
 * NOTE: on success, the returned object is a syscache entry.  The caller
 * must ReleaseSysCache() the entry when done with it.
 */
Operator
oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
       bool noError, int location)
{
      Oid               operOid;
      OprCacheKey key;
      bool        key_ok;
      FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
      HeapTuple   tup = NULL;

      /*
       * Try to find the mapping in the lookaside cache.
       */
      key_ok = make_oper_cache_key(&key, opname, ltypeId, rtypeId);
      if (key_ok)
      {
            operOid = find_oper_cache_entry(&key);
            if (OidIsValid(operOid))
            {
                  tup = SearchSysCache(OPEROID,
                                                 ObjectIdGetDatum(operOid),
                                                 0, 0, 0);
                  if (HeapTupleIsValid(tup))
                        return (Operator) tup;
            }
      }

      /*
       * First try for an "exact" match.
       */
      operOid = binary_oper_exact(opname, ltypeId, rtypeId);
      if (!OidIsValid(operOid))
      {
            /*
             * Otherwise, search for the most suitable candidate.
             */
            FuncCandidateList clist;

            /* Get binary operators of given name */
            clist = OpernameGetCandidates(opname, 'b');

            /* No operators found? Then fail... */
            if (clist != NULL)
            {
                  /*
                   * Unspecified type for one of the arguments? then use the other
                   * (XXX this is probably dead code?)
                   */
                  Oid               inputOids[2];

                  if (rtypeId == InvalidOid)
                        rtypeId = ltypeId;
                  else if (ltypeId == InvalidOid)
                        ltypeId = rtypeId;
                  inputOids[0] = ltypeId;
                  inputOids[1] = rtypeId;
                  fdresult = oper_select_candidate(2, inputOids, clist, &operOid);
            }
      }

      if (OidIsValid(operOid))
            tup = SearchSysCache(OPEROID,
                                           ObjectIdGetDatum(operOid),
                                           0, 0, 0);

      if (HeapTupleIsValid(tup))
      {
            if (key_ok)
                  make_oper_cache_entry(&key, operOid);
      }
      else if (!noError)
            op_error(pstate, opname, 'b', ltypeId, rtypeId, fdresult, location);

      return (Operator) tup;
}

/* compatible_oper()
 *    given an opname and input datatypes, find a compatible binary operator
 *
 *    This is tighter than oper() because it will not return an operator that
 *    requires coercion of the input datatypes (but binary-compatible operators
 *    are accepted).    Otherwise, the semantics are the same.
 */
Operator
compatible_oper(ParseState *pstate, List *op, Oid arg1, Oid arg2,
                        bool noError, int location)
{
      Operator    optup;
      Form_pg_operator opform;

      /* oper() will find the best available match */
      optup = oper(pstate, op, arg1, arg2, noError, location);
      if (optup == (Operator) NULL)
            return (Operator) NULL; /* must be noError case */

      /* but is it good enough? */
      opform = (Form_pg_operator) GETSTRUCT(optup);
      if (IsBinaryCoercible(arg1, opform->oprleft) &&
            IsBinaryCoercible(arg2, opform->oprright))
            return optup;

      /* nope... */
      ReleaseSysCache(optup);

      if (!noError)
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("operator requires run-time type coercion: %s",
                                    op_signature_string(op, 'b', arg1, arg2)),
                         parser_errposition(pstate, location)));

      return (Operator) NULL;
}

/* compatible_oper_opid() -- get OID of a binary operator
 *
 * This is a convenience routine that extracts only the operator OID
 * from the result of compatible_oper().  InvalidOid is returned if the
 * lookup fails and noError is true.
 */
Oid
compatible_oper_opid(List *op, Oid arg1, Oid arg2, bool noError)
{
      Operator    optup;
      Oid               result;

      optup = compatible_oper(NULL, op, arg1, arg2, noError, -1);
      if (optup != NULL)
      {
            result = oprid(optup);
            ReleaseSysCache(optup);
            return result;
      }
      return InvalidOid;
}


/* right_oper() -- search for a unary right operator (postfix operator)
 * Given operator name and type of arg, return oper struct.
 *
 * IMPORTANT: the returned operator (if any) is only promised to be
 * coercion-compatible with the input datatype.  Do not use this if
 * you need an exact- or binary-compatible match.
 *
 * If no matching operator found, return NULL if noError is true,
 * raise an error if it is false.  pstate and location are used only to report
 * the error position; pass NULL/-1 if not available.
 *
 * NOTE: on success, the returned object is a syscache entry.  The caller
 * must ReleaseSysCache() the entry when done with it.
 */
Operator
right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
{
      Oid               operOid;
      OprCacheKey key;
      bool        key_ok;
      FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
      HeapTuple   tup = NULL;

      /*
       * Try to find the mapping in the lookaside cache.
       */
      key_ok = make_oper_cache_key(&key, op, arg, InvalidOid);
      if (key_ok)
      {
            operOid = find_oper_cache_entry(&key);
            if (OidIsValid(operOid))
            {
                  tup = SearchSysCache(OPEROID,
                                                 ObjectIdGetDatum(operOid),
                                                 0, 0, 0);
                  if (HeapTupleIsValid(tup))
                        return (Operator) tup;
            }
      }

      /*
       * First try for an "exact" match.
       */
      operOid = OpernameGetOprid(op, arg, InvalidOid);
      if (!OidIsValid(operOid))
      {
            /*
             * Otherwise, search for the most suitable candidate.
             */
            FuncCandidateList clist;

            /* Get postfix operators of given name */
            clist = OpernameGetCandidates(op, 'r');

            /* No operators found? Then fail... */
            if (clist != NULL)
            {
                  /*
                   * We must run oper_select_candidate even if only one candidate,
                   * otherwise we may falsely return a non-type-compatible operator.
                   */
                  fdresult = oper_select_candidate(1, &arg, clist, &operOid);
            }
      }

      if (OidIsValid(operOid))
            tup = SearchSysCache(OPEROID,
                                           ObjectIdGetDatum(operOid),
                                           0, 0, 0);

      if (HeapTupleIsValid(tup))
      {
            if (key_ok)
                  make_oper_cache_entry(&key, operOid);
      }
      else if (!noError)
            op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location);

      return (Operator) tup;
}


/* left_oper() -- search for a unary left operator (prefix operator)
 * Given operator name and type of arg, return oper struct.
 *
 * IMPORTANT: the returned operator (if any) is only promised to be
 * coercion-compatible with the input datatype.  Do not use this if
 * you need an exact- or binary-compatible match.
 *
 * If no matching operator found, return NULL if noError is true,
 * raise an error if it is false.  pstate and location are used only to report
 * the error position; pass NULL/-1 if not available.
 *
 * NOTE: on success, the returned object is a syscache entry.  The caller
 * must ReleaseSysCache() the entry when done with it.
 */
Operator
left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
{
      Oid               operOid;
      OprCacheKey key;
      bool        key_ok;
      FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
      HeapTuple   tup = NULL;

      /*
       * Try to find the mapping in the lookaside cache.
       */
      key_ok = make_oper_cache_key(&key, op, InvalidOid, arg);
      if (key_ok)
      {
            operOid = find_oper_cache_entry(&key);
            if (OidIsValid(operOid))
            {
                  tup = SearchSysCache(OPEROID,
                                                 ObjectIdGetDatum(operOid),
                                                 0, 0, 0);
                  if (HeapTupleIsValid(tup))
                        return (Operator) tup;
            }
      }

      /*
       * First try for an "exact" match.
       */
      operOid = OpernameGetOprid(op, InvalidOid, arg);
      if (!OidIsValid(operOid))
      {
            /*
             * Otherwise, search for the most suitable candidate.
             */
            FuncCandidateList clist;

            /* Get prefix operators of given name */
            clist = OpernameGetCandidates(op, 'l');

            /* No operators found? Then fail... */
            if (clist != NULL)
            {
                  /*
                   * The returned list has args in the form (0, oprright). Move the
                   * useful data into args[0] to keep oper_select_candidate simple.
                   * XXX we are assuming here that we may scribble on the list!
                   */
                  FuncCandidateList clisti;

                  for (clisti = clist; clisti != NULL; clisti = clisti->next)
                  {
                        clisti->args[0] = clisti->args[1];
                  }

                  /*
                   * We must run oper_select_candidate even if only one candidate,
                   * otherwise we may falsely return a non-type-compatible operator.
                   */
                  fdresult = oper_select_candidate(1, &arg, clist, &operOid);
            }
      }

      if (OidIsValid(operOid))
            tup = SearchSysCache(OPEROID,
                                           ObjectIdGetDatum(operOid),
                                           0, 0, 0);

      if (HeapTupleIsValid(tup))
      {
            if (key_ok)
                  make_oper_cache_entry(&key, operOid);
      }
      else if (!noError)
            op_error(pstate, op, 'l', InvalidOid, arg, fdresult, location);

      return (Operator) tup;
}

/*
 * op_signature_string
 *          Build a string representing an operator name, including arg type(s).
 *          The result is something like "integer + integer".
 *
 * This is typically used in the construction of operator-not-found error
 * messages.
 */
static const char *
op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2)
{
      StringInfoData argbuf;

      initStringInfo(&argbuf);

      if (oprkind != 'l')
            appendStringInfo(&argbuf, "%s ", format_type_be(arg1));

      appendStringInfoString(&argbuf, NameListToString(op));

      if (oprkind != 'r')
            appendStringInfo(&argbuf, " %s", format_type_be(arg2));

      return argbuf.data;                 /* return palloc'd string buffer */
}

/*
 * op_error - utility routine to complain about an unresolvable operator
 */
static void
op_error(ParseState *pstate, List *op, char oprkind,
             Oid arg1, Oid arg2,
             FuncDetailCode fdresult, int location)
{
      if (fdresult == FUNCDETAIL_MULTIPLE)
            ereport(ERROR,
                        (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
                         errmsg("operator is not unique: %s",
                                    op_signature_string(op, oprkind, arg1, arg2)),
                         errhint("Could not choose a best candidate operator. "
                                     "You might need to add explicit type casts."),
                         parser_errposition(pstate, location)));
      else
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("operator does not exist: %s",
                                    op_signature_string(op, oprkind, arg1, arg2)),
              errhint("No operator matches the given name and argument type(s). "
                          "You might need to add explicit type casts."),
                         parser_errposition(pstate, location)));
}

/*
 * make_op()
 *          Operator expression construction.
 *
 * Transform operator expression ensuring type compatibility.
 * This is where some type conversion happens.
 *
 * As with coerce_type, pstate may be NULL if no special unknown-Param
 * processing is wanted.
 */
Expr *
make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
            int location)
{
      Oid               ltypeId,
                        rtypeId;
      Operator    tup;
      Form_pg_operator opform;
      Oid               actual_arg_types[2];
      Oid               declared_arg_types[2];
      int               nargs;
      List     *args;
      Oid               rettype;
      OpExpr         *result;

      /* Select the operator */
      if (rtree == NULL)
      {
            /* right operator */
            ltypeId = exprType(ltree);
            rtypeId = InvalidOid;
            tup = right_oper(pstate, opname, ltypeId, false, location);
      }
      else if (ltree == NULL)
      {
            /* left operator */
            rtypeId = exprType(rtree);
            ltypeId = InvalidOid;
            tup = left_oper(pstate, opname, rtypeId, false, location);
      }
      else
      {
            /* otherwise, binary operator */
            ltypeId = exprType(ltree);
            rtypeId = exprType(rtree);
            tup = oper(pstate, opname, ltypeId, rtypeId, false, location);
      }

      opform = (Form_pg_operator) GETSTRUCT(tup);

      /* Check it's not a shell */
      if (!RegProcedureIsValid(opform->oprcode))
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("operator is only a shell: %s",
                                    op_signature_string(opname,
                                                                  opform->oprkind,
                                                                  opform->oprleft,
                                                                  opform->oprright)),
                         parser_errposition(pstate, location)));

      /* Do typecasting and build the expression tree */
      if (rtree == NULL)
      {
            /* right operator */
            args = list_make1(ltree);
            actual_arg_types[0] = ltypeId;
            declared_arg_types[0] = opform->oprleft;
            nargs = 1;
      }
      else if (ltree == NULL)
      {
            /* left operator */
            args = list_make1(rtree);
            actual_arg_types[0] = rtypeId;
            declared_arg_types[0] = opform->oprright;
            nargs = 1;
      }
      else
      {
            /* otherwise, binary operator */
            args = list_make2(ltree, rtree);
            actual_arg_types[0] = ltypeId;
            actual_arg_types[1] = rtypeId;
            declared_arg_types[0] = opform->oprleft;
            declared_arg_types[1] = opform->oprright;
            nargs = 2;
      }

      /*
       * enforce consistency with polymorphic argument and return types,
       * possibly adjusting return type or declared_arg_types (which will be
       * used as the cast destination by make_fn_arguments)
       */
      rettype = enforce_generic_type_consistency(actual_arg_types,
                                                                     declared_arg_types,
                                                                     nargs,
                                                                     opform->oprresult,
                                                                     false);

      /* perform the necessary typecasting of arguments */
      make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);

      /* and build the expression node */
      result = makeNode(OpExpr);
      result->opno = oprid(tup);
      result->opfuncid = opform->oprcode;
      result->opresulttype = rettype;
      result->opretset = get_func_retset(opform->oprcode);
      result->args = args;
      result->location = location;

      ReleaseSysCache(tup);

      return (Expr *) result;
}

/*
 * make_scalar_array_op()
 *          Build expression tree for "scalar op ANY/ALL (array)" construct.
 */
Expr *
make_scalar_array_op(ParseState *pstate, List *opname,
                               bool useOr,
                               Node *ltree, Node *rtree,
                               int location)
{
      Oid               ltypeId,
                        rtypeId,
                        atypeId,
                        res_atypeId;
      Operator    tup;
      Form_pg_operator opform;
      Oid               actual_arg_types[2];
      Oid               declared_arg_types[2];
      List     *args;
      Oid               rettype;
      ScalarArrayOpExpr *result;

      ltypeId = exprType(ltree);
      atypeId = exprType(rtree);

      /*
       * The right-hand input of the operator will be the element type of the
       * array.  However, if we currently have just an untyped literal on the
       * right, stay with that and hope we can resolve the operator.
       */
      if (atypeId == UNKNOWNOID)
            rtypeId = UNKNOWNOID;
      else
      {
            rtypeId = get_element_type(atypeId);
            if (!OidIsValid(rtypeId))
                  ereport(ERROR,
                              (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                           errmsg("op ANY/ALL (array) requires array on right side"),
                               parser_errposition(pstate, location)));
      }

      /* Now resolve the operator */
      tup = oper(pstate, opname, ltypeId, rtypeId, false, location);
      opform = (Form_pg_operator) GETSTRUCT(tup);

      /* Check it's not a shell */
      if (!RegProcedureIsValid(opform->oprcode))
            ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
                         errmsg("operator is only a shell: %s",
                                    op_signature_string(opname,
                                                                  opform->oprkind,
                                                                  opform->oprleft,
                                                                  opform->oprright)),
                         parser_errposition(pstate, location)));

      args = list_make2(ltree, rtree);
      actual_arg_types[0] = ltypeId;
      actual_arg_types[1] = rtypeId;
      declared_arg_types[0] = opform->oprleft;
      declared_arg_types[1] = opform->oprright;

      /*
       * enforce consistency with polymorphic argument and return types,
       * possibly adjusting return type or declared_arg_types (which will be
       * used as the cast destination by make_fn_arguments)
       */
      rettype = enforce_generic_type_consistency(actual_arg_types,
                                                                     declared_arg_types,
                                                                     2,
                                                                     opform->oprresult,
                                                                     false);

      /*
       * Check that operator result is boolean
       */
      if (rettype != BOOLOID)
            ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                   errmsg("op ANY/ALL (array) requires operator to yield boolean"),
                         parser_errposition(pstate, location)));
      if (get_func_retset(opform->oprcode))
            ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
              errmsg("op ANY/ALL (array) requires operator not to return a set"),
                         parser_errposition(pstate, location)));

      /*
       * Now switch back to the array type on the right, arranging for any
       * needed cast to be applied.  Beware of polymorphic operators here;
       * enforce_generic_type_consistency may or may not have replaced a
       * polymorphic type with a real one.
       */
      if (IsPolymorphicType(declared_arg_types[1]))
      {
            /* assume the actual array type is OK */
            res_atypeId = atypeId;
      }
      else
      {
            res_atypeId = get_array_type(declared_arg_types[1]);
            if (!OidIsValid(res_atypeId))
                  ereport(ERROR,
                              (errcode(ERRCODE_UNDEFINED_OBJECT),
                               errmsg("could not find array type for data type %s",
                                          format_type_be(declared_arg_types[1])),
                               parser_errposition(pstate, location)));
      }
      actual_arg_types[1] = atypeId;
      declared_arg_types[1] = res_atypeId;

      /* perform the necessary typecasting of arguments */
      make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);

      /* and build the expression node */
      result = makeNode(ScalarArrayOpExpr);
      result->opno = oprid(tup);
      result->opfuncid = opform->oprcode;
      result->useOr = useOr;
      result->args = args;
      result->location = location;

      ReleaseSysCache(tup);

      return (Expr *) result;
}


/*
 * Lookaside cache to speed operator lookup.  Possibly this should be in
 * a separate module under utils/cache/ ?
 *
 * The idea here is that the mapping from operator name and given argument
 * types is constant for a given search path (or single specified schema OID)
 * so long as the contents of pg_operator and pg_cast don't change.  And that
 * mapping is pretty expensive to compute, especially for ambiguous operators;
 * this is mainly because there are a *lot* of instances of popular operator
 * names such as "=", and we have to check each one to see which is the
 * best match.    So once we have identified the correct mapping, we save it
 * in a cache that need only be flushed on pg_operator or pg_cast change.
 * (pg_cast must be considered because changes in the set of implicit casts
 * affect the set of applicable operators for any given input datatype.)
 *
 * XXX in principle, ALTER TABLE ... INHERIT could affect the mapping as
 * well, but we disregard that since there's no convenient way to find out
 * about it, and it seems a pretty far-fetched corner-case anyway.
 *
 * Note: at some point it might be worth doing a similar cache for function
 * lookups.  However, the potential gain is a lot less since (a) function
 * names are generally not overloaded as heavily as operator names, and
 * (b) we'd have to flush on pg_proc updates, which are probably a good
 * deal more common than pg_operator updates.
 */

/* The operator cache hashtable */
static HTAB *OprCacheHash = NULL;


/*
 * make_oper_cache_key
 *          Fill the lookup key struct given operator name and arg types.
 *
 * Returns TRUE if successful, FALSE if the search_path overflowed
 * (hence no caching is possible).
 */
static bool
make_oper_cache_key(OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId)
{
      char     *schemaname;
      char     *opername;

      /* deconstruct the name list */
      DeconstructQualifiedName(opname, &schemaname, &opername);

      /* ensure zero-fill for stable hashing */
      MemSet(key, 0, sizeof(OprCacheKey));

      /* save operator name and input types into key */
      strlcpy(key->oprname, opername, NAMEDATALEN);
      key->left_arg = ltypeId;
      key->right_arg = rtypeId;

      if (schemaname)
      {
            /* search only in exact schema given */
            key->search_path[0] = LookupExplicitNamespace(schemaname);
      }
      else
      {
            /* get the active search path */
            if (fetch_search_path_array(key->search_path,
                                                  MAX_CACHED_PATH_LEN) > MAX_CACHED_PATH_LEN)
                  return false;           /* oops, didn't fit */
      }

      return true;
}

/*
 * find_oper_cache_entry
 *
 * Look for a cache entry matching the given key.  If found, return the
 * contained operator OID, else return InvalidOid.
 */
static Oid
find_oper_cache_entry(OprCacheKey *key)
{
      OprCacheEntry *oprentry;

      if (OprCacheHash == NULL)
      {
            /* First time through: initialize the hash table */
            HASHCTL           ctl;

            if (!CacheMemoryContext)
                  CreateCacheMemoryContext();

            MemSet(&ctl, 0, sizeof(ctl));
            ctl.keysize = sizeof(OprCacheKey);
            ctl.entrysize = sizeof(OprCacheEntry);
            ctl.hash = tag_hash;
            OprCacheHash = hash_create("Operator lookup cache", 256,
                                                   &ctl, HASH_ELEM | HASH_FUNCTION);

            /* Arrange to flush cache on pg_operator and pg_cast changes */
            CacheRegisterSyscacheCallback(OPERNAMENSP,
                                                        InvalidateOprCacheCallBack,
                                                        (Datum) 0);
            CacheRegisterSyscacheCallback(CASTSOURCETARGET,
                                                        InvalidateOprCacheCallBack,
                                                        (Datum) 0);
      }

      /* Look for an existing entry */
      oprentry = (OprCacheEntry *) hash_search(OprCacheHash,
                                                                   (void *) key,
                                                                   HASH_FIND, NULL);
      if (oprentry == NULL)
            return InvalidOid;

      return oprentry->opr_oid;
}

/*
 * make_oper_cache_entry
 *
 * Insert a cache entry for the given key.
 */
static void
make_oper_cache_entry(OprCacheKey *key, Oid opr_oid)
{
      OprCacheEntry *oprentry;

      Assert(OprCacheHash != NULL);

      oprentry = (OprCacheEntry *) hash_search(OprCacheHash,
                                                                   (void *) key,
                                                                   HASH_ENTER, NULL);
      oprentry->opr_oid = opr_oid;
}

/*
 * Callback for pg_operator and pg_cast inval events
 */
static void
InvalidateOprCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
{
      HASH_SEQ_STATUS status;
      OprCacheEntry *hentry;

      Assert(OprCacheHash != NULL);

      /* Currently we just flush all entries; hard to be smarter ... */
      hash_seq_init(&status, OprCacheHash);

      while ((hentry = (OprCacheEntry *) hash_seq_search(&status)) != NULL)
      {
            if (hash_search(OprCacheHash,
                                    (void *) &hentry->key,
                                    HASH_REMOVE, NULL) == NULL)
                  elog(ERROR, "hash table corrupted");
      }
}

Generated by  Doxygen 1.6.0   Back to index