diff --git a/doc/src/sgml/ref/create_domain.sgml b/doc/src/sgml/ref/create_domain.sgml index 82a0b87492..73f9f28d6c 100644 --- a/doc/src/sgml/ref/create_domain.sgml +++ b/doc/src/sgml/ref/create_domain.sgml @@ -239,6 +239,11 @@ INSERT INTO tab (domcol) VALUES ((SELECT domcol FROM tab WHERE false)); DOMAIN), adjust the function definition, and re-add the constraint, thereby rechecking it against stored data. + + + It's also good practice to ensure that domain CHECK + expressions will not throw errors. + diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index 3de0cb01a2..99aeaddb5d 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -126,9 +126,14 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) * 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 ErrorStateContext, 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) +domain_check_input(Datum value, bool isnull, DomainIOData *my_extra, + Node *escontext) { ExprContext *econtext = my_extra->econtext; ListCell *l; @@ -144,11 +149,14 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) { case DOM_CONSTRAINT_NOTNULL: if (isnull) - ereport(ERROR, + { + 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: { @@ -179,13 +187,16 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) econtext->domainValue_isNull = isnull; if (!ExecCheck(con->check_exprstate, econtext)) - ereport(ERROR, + { + 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: @@ -200,6 +211,7 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) * per-tuple memory. This avoids leaking non-memory resources, if * anything in the expression(s) has any. */ +fail: if (econtext) ReScanExprContext(econtext); } @@ -213,6 +225,7 @@ domain_in(PG_FUNCTION_ARGS) { char *string; Oid domainType; + Node *escontext = fcinfo->context; DomainIOData *my_extra; Datum value; @@ -245,15 +258,18 @@ domain_in(PG_FUNCTION_ARGS) /* * Invoke the base type's typinput procedure to convert the data. */ - value = InputFunctionCall(&my_extra->proc, - string, - my_extra->typioparam, - my_extra->typtypmod); + 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); + domain_check_input(value, (string == NULL), my_extra, escontext); if (string == NULL) PG_RETURN_NULL(); @@ -309,7 +325,7 @@ domain_recv(PG_FUNCTION_ARGS) /* * Do the necessary checks to ensure it's a valid domain value. */ - domain_check_input(value, (buf == NULL), my_extra); + domain_check_input(value, (buf == NULL), my_extra, NULL); if (buf == NULL) PG_RETURN_NULL(); @@ -349,7 +365,7 @@ domain_check(Datum value, bool isnull, Oid domainType, /* * Do the necessary checks to ensure it's a valid domain value. */ - domain_check_input(value, isnull, my_extra); + domain_check_input(value, isnull, my_extra, NULL); } /* diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index 73b010f6ed..25f6bb9e1f 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -87,6 +87,56 @@ drop domain domainvarchar restrict; drop domain domainnumeric restrict; drop domain domainint4 restrict; drop domain domaintext; +-- Test non-error-throwing input +create domain positiveint int4 check(value > 0); +create domain weirdfloat float8 check((1 / value) < 10); +select pg_input_is_valid('1', 'positiveint'); + pg_input_is_valid +------------------- + t +(1 row) + +select pg_input_is_valid('junk', 'positiveint'); + pg_input_is_valid +------------------- + f +(1 row) + +select pg_input_is_valid('-1', 'positiveint'); + pg_input_is_valid +------------------- + f +(1 row) + +select pg_input_error_message('junk', 'positiveint'); + pg_input_error_message +----------------------------------------------- + invalid input syntax for type integer: "junk" +(1 row) + +select pg_input_error_message('-1', 'positiveint'); + pg_input_error_message +---------------------------------------------------------------------------- + value for domain positiveint violates check constraint "positiveint_check" +(1 row) + +select pg_input_error_message('junk', 'weirdfloat'); + pg_input_error_message +-------------------------------------------------------- + invalid input syntax for type double precision: "junk" +(1 row) + +select pg_input_error_message('0.01', 'weirdfloat'); + pg_input_error_message +-------------------------------------------------------------------------- + value for domain weirdfloat violates check constraint "weirdfloat_check" +(1 row) + +-- We currently can't trap errors raised in the CHECK expression itself +select pg_input_error_message('0', 'weirdfloat'); +ERROR: division by zero +drop domain positiveint; +drop domain weirdfloat; -- Test domains over array types create domain domainint4arr int4[1]; create domain domainchar4arr varchar(4)[2][3]; diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql index f2ca1fb675..1558bd9a33 100644 --- a/src/test/regress/sql/domain.sql +++ b/src/test/regress/sql/domain.sql @@ -69,6 +69,25 @@ drop domain domainint4 restrict; drop domain domaintext; +-- Test non-error-throwing input + +create domain positiveint int4 check(value > 0); +create domain weirdfloat float8 check((1 / value) < 10); + +select pg_input_is_valid('1', 'positiveint'); +select pg_input_is_valid('junk', 'positiveint'); +select pg_input_is_valid('-1', 'positiveint'); +select pg_input_error_message('junk', 'positiveint'); +select pg_input_error_message('-1', 'positiveint'); +select pg_input_error_message('junk', 'weirdfloat'); +select pg_input_error_message('0.01', 'weirdfloat'); +-- We currently can't trap errors raised in the CHECK expression itself +select pg_input_error_message('0', 'weirdfloat'); + +drop domain positiveint; +drop domain weirdfloat; + + -- Test domains over array types create domain domainint4arr int4[1];