postgresql/src/backend/utils/adt/domains.c

297 lines
8.1 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* 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 two mechanisms for minimizing this cost:
* 1. In a nest of domains, we flatten the checking of all the levels
* into just one operation.
* 2. We cache the list of constraint items in the FmgrInfo struct
* passed by the caller.
*
* We also have to create an EState to evaluate CHECK expressions in.
* Creating and destroying an EState is somewhat expensive, and so it's
* tempting to cache the EState too. However, that would mean that the
* EState never gets an explicit FreeExecutorState call, which is a bad idea
* because it risks leaking non-memory resources.
*
*
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/domains.c,v 1.2 2006/07/14 14:52:24 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
#include "utils/builtins.h"
#include "utils/lsyscache.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;
/* List of constraint items to check */
List *constraint_list;
} DomainIOData;
/*
* domain_state_setup - initialize the cache for a new domain type.
*/
static void
domain_state_setup(DomainIOData *my_extra, Oid domainType, bool binary,
MemoryContext mcxt)
{
Oid baseType;
MemoryContext oldcontext;
/* Mark cache invalid */
my_extra->domain_type = InvalidOid;
/* Find out the base type */
my_extra->typtypmod = -1;
baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
if (baseType == domainType)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type %s is not a domain",
format_type_be(domainType))));
/* 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 */
oldcontext = MemoryContextSwitchTo(mcxt);
my_extra->constraint_list = GetDomainConstraints(domainType);
MemoryContextSwitchTo(oldcontext);
/* Mark cache valid */
my_extra->domain_type = domainType;
}
/*
* 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)
{
EState *estate = NULL;
ListCell *l;
foreach(l, my_extra->constraint_list)
{
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))));
break;
case DOM_CONSTRAINT_CHECK:
{
ExprContext *econtext;
Datum conResult;
bool conIsNull;
Datum save_datum;
bool save_isNull;
if (estate == NULL)
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
/*
* Set up value to be returned by CoerceToDomainValue
* nodes. We must save and restore prior setting of
* econtext's domainValue fields, in case this node is
* itself within a check expression for another domain.
*/
save_datum = econtext->domainValue_datum;
save_isNull = econtext->domainValue_isNull;
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)));
econtext->domainValue_datum = save_datum;
econtext->domainValue_isNull = save_isNull;
break;
}
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->constrainttype);
break;
}
}
if (estate)
FreeExecutorState(estate);
}
/*
* 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.
*/
my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
if (my_extra == NULL)
{
my_extra = (DomainIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
sizeof(DomainIOData));
domain_state_setup(my_extra, domainType, false,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) my_extra;
}
else if (my_extra->domain_type != domainType)
domain_state_setup(my_extra, domainType, false,
fcinfo->flinfo->fn_mcxt);
/*
* 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.
*/
my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra;
if (my_extra == NULL)
{
my_extra = (DomainIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
sizeof(DomainIOData));
domain_state_setup(my_extra, domainType, true,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) my_extra;
}
else if (my_extra->domain_type != domainType)
domain_state_setup(my_extra, domainType, true,
fcinfo->flinfo->fn_mcxt);
/*
* 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);
}