postgresql/src/backend/utils/adt/domains.c
Tom Lane 967a7b0fc9 Avoid reporting "cache lookup failed" for some user-reachable cases.
We have a not-terribly-thoroughly-enforced-yet project policy that internal
errors with SQLSTATE XX000 (ie, plain elog) should not be triggerable from
SQL.  record_in, domain_in, and PL validator functions all failed to meet
this standard, because they threw plain elog("cache lookup failed for XXX")
errors on bad OIDs, and those are all invokable from SQL.

For record_in, the best fix is to upgrade typcache.c (lookup_type_cache)
to throw a user-facing error for this case.  That seems consistent because
it was more than halfway there already, having user-facing errors for shell
types and non-composite types.  Having done that, tweak domain_in to rely
on the typcache to throw an appropriate error.  (This costs little because
InitDomainConstraintRef would fetch the typcache entry anyway.)

For the PL validator functions, we already have a single choke point at
CheckFunctionValidatorAccess, so just fix its error to be user-facing.

Dilip Kumar, reviewed by Haribabu Kommi

Discussion: <87wpxfygg9.fsf@credativ.de>
2016-09-09 09:20:34 -04:00

389 lines
11 KiB
C

/*-------------------------------------------------------------------------
*
* domains.c
* I/O functions for domain types.
*
* The output functions for a domain type are just the same ones provided
* by its underlying base type. The input functions, however, must be
* prepared to apply any constraints defined by the type. So, we create
* special input functions that invoke the base type's input function
* and then check the constraints.
*
* The overhead required for constraint checking can be high, since examining
* the catalogs to discover the constraints for a given domain is not cheap.
* We have three mechanisms for minimizing this cost:
* 1. We rely on the typcache to keep up-to-date copies of the constraints.
* 2. In a nest of domains, we flatten the checking of all the levels
* into just one operation (the typcache does this for us).
* 3. If there are CHECK constraints, we cache a standalone ExprContext
* to evaluate them in.
*
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/adt/domains.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "lib/stringinfo.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
/*
* structure to cache state across multiple calls
*/
typedef struct DomainIOData
{
Oid domain_type;
/* Data needed to call base type's input function */
Oid typiofunc;
Oid typioparam;
int32 typtypmod;
FmgrInfo proc;
/* Reference to cached list of constraint items to check */
DomainConstraintRef constraint_ref;
/* Context for evaluating CHECK constraints in */
ExprContext *econtext;
/* Memory context this cache is in */
MemoryContext mcxt;
} DomainIOData;
/*
* domain_state_setup - initialize the cache for a new domain type.
*
* Note: we can't re-use the same cache struct for a new domain type,
* since there's no provision for releasing the DomainConstraintRef.
* If a call site needs to deal with a new domain type, we just leak
* the old struct for the duration of the query.
*/
static DomainIOData *
domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
{
DomainIOData *my_extra;
TypeCacheEntry *typentry;
Oid baseType;
my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData));
/*
* Verify that domainType represents a valid domain type. We need to be
* careful here because domain_in and domain_recv can be called from SQL,
* possibly with incorrect arguments. We use lookup_type_cache mainly
* because it will throw a clean user-facing error for a bad OID.
*/
typentry = lookup_type_cache(domainType, 0);
if (typentry->typtype != TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type %s is not a domain",
format_type_be(domainType))));
/* Find out the base type */
my_extra->typtypmod = -1;
baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
/* Look up underlying I/O function */
if (binary)
getTypeBinaryInputInfo(baseType,
&my_extra->typiofunc,
&my_extra->typioparam);
else
getTypeInputInfo(baseType,
&my_extra->typiofunc,
&my_extra->typioparam);
fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
/* Look up constraints for domain */
InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt);
/* We don't make an ExprContext until needed */
my_extra->econtext = NULL;
my_extra->mcxt = mcxt;
/* Mark cache valid */
my_extra->domain_type = domainType;
return my_extra;
}
/*
* domain_check_input - apply the cached checks.
*
* This is extremely similar to ExecEvalCoerceToDomain in execQual.c.
*/
static void
domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
{
ExprContext *econtext = my_extra->econtext;
ListCell *l;
/* Make sure we have up-to-date constraints */
UpdateDomainConstraintRef(&my_extra->constraint_ref);
foreach(l, my_extra->constraint_ref.constraints)
{
DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
switch (con->constrainttype)
{
case DOM_CONSTRAINT_NOTNULL:
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("domain %s does not allow null values",
format_type_be(my_extra->domain_type)),
errdatatype(my_extra->domain_type)));
break;
case DOM_CONSTRAINT_CHECK:
{
Datum conResult;
bool conIsNull;
/* Make the econtext if we didn't already */
if (econtext == NULL)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
econtext = CreateStandaloneExprContext();
MemoryContextSwitchTo(oldcontext);
my_extra->econtext = econtext;
}
/*
* Set up value to be returned by CoerceToDomainValue
* nodes. Unlike ExecEvalCoerceToDomain, this econtext
* couldn't be shared with anything else, so no need to
* save and restore fields.
*/
econtext->domainValue_datum = value;
econtext->domainValue_isNull = isnull;
conResult = ExecEvalExprSwitchContext(con->check_expr,
econtext,
&conIsNull, NULL);
if (!conIsNull &&
!DatumGetBool(conResult))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("value for domain %s violates check constraint \"%s\"",
format_type_be(my_extra->domain_type),
con->name),
errdomainconstraint(my_extra->domain_type,
con->name)));
break;
}
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->constrainttype);
break;
}
}
/*
* Before exiting, call any shutdown callbacks and reset econtext's
* per-tuple memory. This avoids leaking non-memory resources, if
* anything in the expression(s) has any.
*/
if (econtext)
ReScanExprContext(econtext);
}
/*
* domain_in - input routine for any domain type.
*/
Datum
domain_in(PG_FUNCTION_ARGS)
{
char *string;
Oid domainType;
DomainIOData *my_extra;
Datum value;
/*
* Since domain_in is not strict, we have to check for null inputs. The
* typioparam argument should never be null in normal system usage, but it
* could be null in a manual invocation --- if so, just return null.
*/
if (PG_ARGISNULL(0))
string = NULL;
else
string = PG_GETARG_CSTRING(0);
if (PG_ARGISNULL(1))
PG_RETURN_NULL();
domainType = PG_GETARG_OID(1);
/*
* We arrange to look up the needed info just once per series of calls,
* assuming the domain type doesn't change underneath us (which really
* shouldn't happen, but cope if it does).
*/
my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
if (my_extra == NULL || my_extra->domain_type != domainType)
{
my_extra = domain_state_setup(domainType, false,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) my_extra;
}
/*
* Invoke the base type's typinput procedure to convert the data.
*/
value = InputFunctionCall(&my_extra->proc,
string,
my_extra->typioparam,
my_extra->typtypmod);
/*
* Do the necessary checks to ensure it's a valid domain value.
*/
domain_check_input(value, (string == NULL), my_extra);
if (string == NULL)
PG_RETURN_NULL();
else
PG_RETURN_DATUM(value);
}
/*
* domain_recv - binary input routine for any domain type.
*/
Datum
domain_recv(PG_FUNCTION_ARGS)
{
StringInfo buf;
Oid domainType;
DomainIOData *my_extra;
Datum value;
/*
* Since domain_recv is not strict, we have to check for null inputs. The
* typioparam argument should never be null in normal system usage, but it
* could be null in a manual invocation --- if so, just return null.
*/
if (PG_ARGISNULL(0))
buf = NULL;
else
buf = (StringInfo) PG_GETARG_POINTER(0);
if (PG_ARGISNULL(1))
PG_RETURN_NULL();
domainType = PG_GETARG_OID(1);
/*
* We arrange to look up the needed info just once per series of calls,
* assuming the domain type doesn't change underneath us (which really
* shouldn't happen, but cope if it does).
*/
my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
if (my_extra == NULL || my_extra->domain_type != domainType)
{
my_extra = domain_state_setup(domainType, true,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) my_extra;
}
/*
* Invoke the base type's typreceive procedure to convert the data.
*/
value = ReceiveFunctionCall(&my_extra->proc,
buf,
my_extra->typioparam,
my_extra->typtypmod);
/*
* Do the necessary checks to ensure it's a valid domain value.
*/
domain_check_input(value, (buf == NULL), my_extra);
if (buf == NULL)
PG_RETURN_NULL();
else
PG_RETURN_DATUM(value);
}
/*
* domain_check - check that a datum satisfies the constraints of a
* domain. extra and mcxt can be passed if they are available from,
* say, a FmgrInfo structure, or they can be NULL, in which case the
* setup is repeated for each call.
*/
void
domain_check(Datum value, bool isnull, Oid domainType,
void **extra, MemoryContext mcxt)
{
DomainIOData *my_extra = NULL;
if (mcxt == NULL)
mcxt = CurrentMemoryContext;
/*
* We arrange to look up the needed info just once per series of calls,
* assuming the domain type doesn't change underneath us (which really
* shouldn't happen, but cope if it does).
*/
if (extra)
my_extra = (DomainIOData *) *extra;
if (my_extra == NULL || my_extra->domain_type != domainType)
{
my_extra = domain_state_setup(domainType, true, mcxt);
if (extra)
*extra = (void *) my_extra;
}
/*
* Do the necessary checks to ensure it's a valid domain value.
*/
domain_check_input(value, isnull, my_extra);
}
/*
* errdatatype --- stores schema_name and datatype_name of a datatype
* within the current errordata.
*/
int
errdatatype(Oid datatypeOid)
{
HeapTuple tup;
Form_pg_type typtup;
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for type %u", datatypeOid);
typtup = (Form_pg_type) GETSTRUCT(tup);
err_generic_string(PG_DIAG_SCHEMA_NAME,
get_namespace_name(typtup->typnamespace));
err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname));
ReleaseSysCache(tup);
return 0; /* return value does not matter */
}
/*
* errdomainconstraint --- stores schema_name, datatype_name and
* constraint_name of a domain-related constraint within the current errordata.
*/
int
errdomainconstraint(Oid datatypeOid, const char *conname)
{
errdatatype(datatypeOid);
err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
return 0; /* return value does not matter */
}