1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-19 13:42:17 +03:00
Files
postgres/src/backend/utils/adt/domains.c
Amit Langote 1edb3b491b Adjust populate_record_field() to handle errors softly
This adds a Node *escontext parameter to it and a bunch of functions
downstream to it, replacing any ereport()s in that path by either
errsave() or ereturn() as appropriate.  This also adds code to those
functions where necessary to return early upon encountering a soft
error.

The changes here are mainly intended to suppress errors in the
functions of jsonfuncs.c.  Functions in any external modules, such as
arrayfuncs.c, that those functions may in turn call are not changed
here based on the assumption that the various checks in jsonfuncs.c
functions should ensure that only values that are structurally valid
get passed to the functions in those external modules.  An exception
is made for domain_check() to allow handling domain constraint
violation errors softly.

For testing, this adds a function jsonb_populate_record_valid(),
which returns true if jsonb_populate_record() would finish without
causing an error for the provided JSON object, false otherwise.  Note
that jsonb_populate_record() internally calls populate_record(),
which in turn uses populate_record_field().

Extracted from a much larger patch to add SQL/JSON query functions.

Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Andrew Dunstan <andrew@dunslane.net>
Author: Amit Langote <amitlangote09@gmail.com>

Reviewers have included (in no particular order) Andres Freund,
Alexander Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers,
Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby,
Álvaro Herrera, Jian He, Peter Eisentraut

Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
Discussion: https://postgr.es/m/CA+HiwqHROpf9e644D8BRqYvaAPmgBZVup-xKMDPk-nd4EpgzHw@mail.gmail.com
Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com
2024-01-24 15:04:33 +09:00

438 lines
12 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-2024, 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/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
static bool domain_check_internal(Datum value, bool isnull, Oid domainType,
void **extra, MemoryContext mcxt,
Node *escontext);
/*
* 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; but also
* it can cache the underlying base type info.
*/
typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
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 */
baseType = typentry->domainBaseType;
my_extra->typtypmod = typentry->domainBaseTypmod;
/* 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, true);
/* 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 roughly similar to the handling of CoerceToDomain nodes in
* execExpr*.c, but we execute each constraint separately, rather than
* compiling them in-line within a larger expression.
*
* If escontext points to an ErrorSaveContext, any failures are reported
* there, otherwise they are ereport'ed. Note that we do not attempt to do
* soft reporting of errors raised during execution of CHECK constraints.
*/
static void
domain_check_input(Datum value, bool isnull, DomainIOData *my_extra,
Node *escontext)
{
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)
{
errsave(escontext,
(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)));
goto fail;
}
break;
case DOM_CONSTRAINT_CHECK:
{
/* 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 in the generic expression case, this
* econtext couldn't be shared with anything else, so no
* need to save and restore fields. But we do need to
* protect the passed-in value against being changed by
* called functions. (It couldn't be a R/W expanded
* object for most uses, but that seems possible for
* domain_check().)
*/
econtext->domainValue_datum =
MakeExpandedObjectReadOnly(value, isnull,
my_extra->constraint_ref.tcache->typlen);
econtext->domainValue_isNull = isnull;
if (!ExecCheck(con->check_exprstate, econtext))
{
errsave(escontext,
(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)));
goto fail;
}
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.
*/
fail:
if (econtext)
ReScanExprContext(econtext);
}
/*
* domain_in - input routine for any domain type.
*/
Datum
domain_in(PG_FUNCTION_ARGS)
{
char *string;
Oid domainType;
Node *escontext = fcinfo->context;
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.
*/
if (!InputFunctionCallSafe(&my_extra->proc,
string,
my_extra->typioparam,
my_extra->typtypmod,
escontext,
&value))
PG_RETURN_NULL();
/*
* Do the necessary checks to ensure it's a valid domain value.
*/
domain_check_input(value, (string == NULL), my_extra, escontext);
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, NULL);
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)
{
(void) domain_check_internal(value, isnull, domainType, extra, mcxt,
NULL);
}
/* Error-safe variant of domain_check(). */
bool
domain_check_safe(Datum value, bool isnull, Oid domainType,
void **extra, MemoryContext mcxt,
Node *escontext)
{
return domain_check_internal(value, isnull, domainType, extra, mcxt,
escontext);
}
/*
* domain_check_internal
* Workhorse for domain_check() and domain_check_safe()
*
* Returns false if an error occurred in domain_check_input() and 'escontext'
* points to an ErrorSaveContext, true otherwise.
*/
static bool
domain_check_internal(Datum value, bool isnull, Oid domainType,
void **extra, MemoryContext mcxt,
Node *escontext)
{
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, escontext);
return !SOFT_ERROR_OCCURRED(escontext);
}
/*
* 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 */
}