1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-20 05:03:10 +03:00

Fix a bunch of problems with domains by making them use special input functions

that apply the necessary domain constraint checks immediately.  This fixes
cases where domain constraints went unchecked for statement parameters,
PL function local variables and results, etc.  We can also eliminate existing
special cases for domains in places that had gotten it right, eg COPY.

Also, allow domains over domains (base of a domain is another domain type).
This almost worked before, but was disallowed because the original patch
hadn't gotten it quite right.
This commit is contained in:
Tom Lane
2006-04-05 22:11:58 +00:00
parent 89a67e523e
commit 7fdb4305db
20 changed files with 549 additions and 196 deletions

View File

@ -0,0 +1,297 @@
/*-------------------------------------------------------------------------
*
* 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.1 2006/04/05 22:11:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
#include "lib/stringinfo.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);
}