/*------------------------------------------------------------------------- * * parse_oper.c * handle operator things for parser * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/parser/parse_oper.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.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/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 */ 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(ParseState *pstate, OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId, int location); 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, uint32 hashvalue); /* * LookupOperName * Given a possibly-qualified operator name and exact input datatypes, * look up the operator. * * Pass oprleft = InvalidOid for a prefix 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 = 'b'; else { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("postfix operators are not supported"), parser_errposition(pstate, location))); oprkind = 0; /* keep compiler quiet */ } 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; } /* * LookupOperWithArgs * Like LookupOperName, but the argument types are specified by * a ObjectWithArgs node. */ Oid LookupOperWithArgs(ObjectWithArgs *oper, bool noError) { TypeName *oprleft, *oprright; Oid leftoid, rightoid; Assert(list_length(oper->objargs) == 2); oprleft = linitial_node(TypeName, oper->objargs); oprright = lsecond_node(TypeName, oper->objargs); if (oprleft == NULL) leftoid = InvalidOid; else leftoid = LookupTypeNameOid(NULL, oprleft, noError); if (oprright == NULL) rightoid = InvalidOid; else rightoid = LookupTypeNameOid(NULL, oprright, noError); return LookupOperName(NULL, oper->objname, leftoid, rightoid, noError, -1); } /* * 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. * * 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, * 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, 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 matching opclasses. */ 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); /* 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; if (isHashable) *isHashable = hashable; } /* given operator tuple, return the operator OID */ Oid oprid(Operator op) { return ((Form_pg_operator) GETSTRUCT(op))->oid; } /* 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(pstate, &key, opname, ltypeId, rtypeId, location); if (key_ok) { operOid = find_oper_cache_entry(&key); if (OidIsValid(operOid)) { tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); 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', false); /* 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 = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); 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; } /* 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(pstate, &key, op, InvalidOid, arg, location); if (key_ok) { operOid = find_oper_cache_entry(&key); if (OidIsValid(operOid)) { tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); 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', false); /* 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 = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); 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)); 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)), (!arg1 || !arg2) ? errhint("No operator matches the given name and argument type. " "You might need to add an explicit type cast.") : errhint("No operator matches the given name and argument types. " "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. * * last_srf should be a copy of pstate->p_last_srf from just before we * started transforming the operator's arguments; this is used for nested-SRF * detection. If the caller will throw an error anyway for a set-returning * expression, it's okay to cheat and just pass pstate->p_last_srf. */ Expr * make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, Node *last_srf, 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; /* Check it's not a postfix operator */ if (rtree == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("postfix operators are not supported"))); /* Select the operator */ if (ltree == NULL) { /* prefix 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 (ltree == NULL) { /* prefix 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); /* opcollid and inputcollid will be set by parse_collate.c */ result->args = args; result->location = location; /* if it returns a set, check that's OK */ if (result->opretset) { check_srf_call_placement(pstate, last_srf, location); /* ... and remember it for error checks at higher levels */ pstate->p_last_srf = (Node *) result; } 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_base_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->hashfuncid = InvalidOid; result->negfuncid = InvalidOid; result->useOr = useOr; /* inputcollid will be set by parse_collate.c */ 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). * * pstate/location are used only to report the error position; pass NULL/-1 * if not available. */ static bool make_oper_cache_key(ParseState *pstate, OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId, int location) { 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) { ParseCallbackState pcbstate; /* search only in exact schema given */ setup_parser_errposition_callback(&pcbstate, pstate, location); key->search_path[0] = LookupExplicitNamespace(schemaname, false); cancel_parser_errposition_callback(&pcbstate); } 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; ctl.keysize = sizeof(OprCacheKey); ctl.entrysize = sizeof(OprCacheEntry); OprCacheHash = hash_create("Operator lookup cache", 256, &ctl, HASH_ELEM | HASH_BLOBS); /* 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, 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, 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, uint32 hashvalue) { 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, &hentry->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); } }