Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p3PpV-0003rP-De for pgsql-hackers@arkaria.postgresql.org; Thu, 08 Dec 2022 22:57:25 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1p3PpU-0006KY-AP for pgsql-hackers@arkaria.postgresql.org; Thu, 08 Dec 2022 22:57:24 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p3PpT-0006DM-F1 for pgsql-hackers@lists.postgresql.org; Thu, 08 Dec 2022 22:57:23 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p3PpM-00023t-KI for pgsql-hackers@lists.postgresql.org; Thu, 08 Dec 2022 22:57:22 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 2B8Mv9dI512552; Thu, 8 Dec 2022 17:57:09 -0500 From: Tom Lane To: Andres Freund cc: Robert Haas , Andrew Dunstan , "David G. Johnston" , Corey Huinker , Alvaro Herrera , Joe Conway , Nikita Glukhov , PostgreSQL Hackers Subject: Re: Error-safe user functions In-reply-to: <20221208215836.3naznqmal2m32hzc@awork3.anarazel.de> References: <3889877.1670358069@sss.pgh.pa.us> <4009543.1670422833@sss.pgh.pa.us> <6acddf68-7cb7-e9e7-a5a7-0abc87b3c55b@dunslane.net> <4111458.1670444176@sss.pgh.pa.us> <3211.1670452341@sss.pgh.pa.us> <20221207233518.sudtaxn6omtvpzvr@awork3.anarazel.de> <186288.1670517119@sss.pgh.pa.us> <20221208215836.3naznqmal2m32hzc@awork3.anarazel.de> Comments: In-reply-to Andres Freund message dated "Thu, 08 Dec 2022 13:58:36 -0800" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <512546.1670540222.0@sss.pgh.pa.us> Date: Thu, 08 Dec 2022 17:57:09 -0500 Message-ID: <512551.1670540229@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <512546.1670540222.1@sss.pgh.pa.us> Andres Freund writes: > On 2022-12-08 16:00:10 -0500, Robert Haas wrote: >> Yes, I think just putting "struct Node;" in as many places as >> necessary is the way to go. Or even: > +1 OK, here's a v5 that does it like that. I've spent a little time pushing ahead on other input functions, and realized that my original plan to require a pre-encoded typmod for these test functions was not very user-friendly. So in v5 you can write something like pg_input_is_valid('1234.567', 'numeric(7,4)') 0004 attached finishes up the remaining core numeric datatypes (int*, float*, numeric). I ripped out float8in_internal_opt_error in favor of a function that uses the new APIs. 0005 converts contrib/cube, which I chose to tackle partly because I'd already touched it in 0004, partly because it seemed like a good idea to verify that extension modules wouldn't have any problems with this apprach, and partly because I wondered whether an input function that uses a Bison/Flex parser would have big problems getting converted. This one didn't, anyway. Given that this additional experimentation didn't find any holes in the API design, I think this is pretty much ready to go. regards, tom lane ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v5-0001-infrastructure.patch"; charset="us-ascii" Content-ID: <512546.1670540222.2@sss.pgh.pa.us> Content-Description: v5-0001-infrastructure.patch Content-Transfer-Encoding: quoted-printable diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_t= ype.sgml index 693423e524..994dfc6526 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -900,6 +900,17 @@ CREATE TYPE name function is written in C. = + + In PostgreSQL version 16 and later, + it is desirable for base types' input functions to + return soft errors using the + new errsave()/ereturn() + mechanism, rather than throwing ereport() + exceptions as in previous versions. + See src/backend/utils/fmgr/README for more + information. + + = diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile index 4368c30fdb..7c594be583 100644 --- a/src/backend/nodes/Makefile +++ b/src/backend/nodes/Makefile @@ -56,6 +56,7 @@ node_headers =3D \ nodes/bitmapset.h \ nodes/extensible.h \ nodes/lockoptions.h \ + nodes/miscnodes.h \ nodes/replnodes.h \ nodes/supportnodes.h \ nodes/value.h \ diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen= _node_support.pl index 7212bc486f..08992dfd47 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -68,6 +68,7 @@ my @all_input_files =3D qw( nodes/bitmapset.h nodes/extensible.h nodes/lockoptions.h + nodes/miscnodes.h nodes/replnodes.h nodes/supportnodes.h nodes/value.h @@ -89,6 +90,7 @@ my @nodetag_only_files =3D qw( executor/tuptable.h foreign/fdwapi.h nodes/lockoptions.h + nodes/miscnodes.h nodes/replnodes.h nodes/supportnodes.h ); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog= .c index f5cd1b7493..eb489ea3a7 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -71,6 +71,7 @@ #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" #include "miscadmin.h" #include "pgstat.h" #include "postmaster/bgworker.h" @@ -611,6 +612,128 @@ errfinish(const char *filename, int lineno, const ch= ar *funcname) CHECK_FOR_INTERRUPTS(); } = + +/* + * errsave_start --- begin a "soft" error-reporting cycle + * + * If "context" isn't an ErrorSaveContext node, this behaves as + * errstart(ERROR, domain), and the errsave() macro ends up acting + * exactly like ereport(ERROR, ...). + * + * If "context" is an ErrorSaveContext node, but the node creator only wa= nts + * notification of the fact of a soft error without any details, we just = set + * the error_occurred flag in the ErrorSaveContext node and return false, + * which will cause us to skip the remaining error processing steps. + * + * Otherwise, create and initialize error stack entry and return true. + * Subsequently, errmsg() and perhaps other routines will be called to fu= rther + * populate the stack entry. Finally, errsave_finish() will be called to + * tidy up. + */ +bool +errsave_start(struct Node *context, const char *domain) +{ + ErrorSaveContext *escontext; + ErrorData *edata; + + /* + * Do we have a context for soft error reporting? If not, just punt to + * errstart(). + */ + if (context =3D=3D NULL || !IsA(context, ErrorSaveContext)) + return errstart(ERROR, domain); + + /* Report that a soft error was detected */ + escontext =3D (ErrorSaveContext *) context; + escontext->error_occurred =3D true; + + /* Nothing else to do if caller wants no further details */ + if (!escontext->details_wanted) + return false; + + /* + * Okay, crank up a stack entry to store the info in. + */ + + recursion_depth++; + + /* Initialize data for this error frame */ + edata =3D get_error_stack_entry(); + edata->elevel =3D LOG; /* signal all is well to errsave_finish */ + set_stack_entry_domain(edata, domain); + /* Select default errcode based on the assumed elevel of ERROR */ + edata->sqlerrcode =3D ERRCODE_INTERNAL_ERROR; + + /* + * Any allocations for this error state level should go into the caller'= s + * context. We don't need to pollute ErrorContext, or even require it t= o + * exist, in this code path. + */ + edata->assoc_context =3D CurrentMemoryContext; + + recursion_depth--; + return true; +} + +/* + * errsave_finish --- end a "soft" error-reporting cycle + * + * If errsave_start() decided this was a regular error, behave as + * errfinish(). Otherwise, package up the error details and save + * them in the ErrorSaveContext node. + */ +void +errsave_finish(struct Node *context, const char *filename, int lineno, + const char *funcname) +{ + ErrorSaveContext *escontext =3D (ErrorSaveContext *) context; + ErrorData *edata =3D &errordata[errordata_stack_depth]; + + /* verify stack depth before accessing *edata */ + CHECK_STACK_DEPTH(); + + /* + * If errsave_start punted to errstart, then elevel will be ERROR or + * perhaps even PANIC. Punt likewise to errfinish. + */ + if (edata->elevel >=3D ERROR) + { + errfinish(filename, lineno, funcname); + pg_unreachable(); + } + + /* + * Else, we should package up the stack entry contents and deliver them = to + * the caller. + */ + recursion_depth++; + + /* Save the last few bits of error state into the stack entry */ + set_stack_entry_location(edata, filename, lineno, funcname); + + /* Replace the LOG value that errsave_start inserted */ + edata->elevel =3D ERROR; + + /* + * We skip calling backtrace and context functions, which are more likel= y + * to cause trouble than provide useful context; they might act on the + * assumption that a transaction abort is about to occur. + */ + + /* + * Make a copy of the error info for the caller. All the subsidiary + * strings are already in the caller's context, so it's sufficient to + * flat-copy the stack entry. + */ + escontext->error_data =3D palloc_object(ErrorData); + memcpy(escontext->error_data, edata, sizeof(ErrorData)); + + /* Exit error-handling context */ + errordata_stack_depth--; + recursion_depth--; +} + + /* * get_error_stack_entry --- allocate and initialize a new stack entry * diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README index 49845f67ac..9958d38992 100644 --- a/src/backend/utils/fmgr/README +++ b/src/backend/utils/fmgr/README @@ -267,6 +267,78 @@ See windowapi.h for more information. information about the context of the CALL statement, particularly whether it is within an "atomic" execution context. = +* Some callers of datatype input functions (and in future perhaps +other classes of functions) pass an instance of ErrorSaveContext. +This indicates that the caller wishes to handle "soft" errors without +a transaction-terminating exception being thrown: instead, the callee +should store information about the error cause in the ErrorSaveContext +struct and return a dummy result value. Further details appear in +"Handling Soft Errors" below. + + +Handling Soft Errors +-------------------- + +Postgres' standard mechanism for reporting errors (ereport() or elog()) +is used for all sorts of error conditions. This means that throwing +an exception via ereport(ERROR) requires an expensive transaction or +subtransaction abort and cleanup, since the exception catcher dare not +make many assumptions about what has gone wrong. There are situations +where we would rather have a lighter-weight mechanism for dealing +with errors that are known to be safe to recover from without a full +transaction cleanup. SQL-callable functions can support this need +using the ErrorSaveContext context mechanism. + +To report a "soft" error, a SQL-callable function should call + errsave(fcinfo->context, ...) +where it would previously have done + ereport(ERROR, ...) +If the passed "context" is NULL or is not an ErrorSaveContext node, +then errsave behaves precisely as ereport(ERROR): the exception is +thrown via longjmp, so that control does not return. If "context" +is an ErrorSaveContext node, then the error information included in +errsave's subsidiary reporting calls is stored into the context node +and control returns from errsave normally. The function should then +return a dummy value to its caller. (SQL NULL is recommendable as +the dummy value; but anything will do, since the caller is expected +to ignore the function's return value once it sees that an error has +been reported in the ErrorSaveContext node.) + +If there is nothing to do except return after calling errsave(), +you can save a line or two by writing + ereturn(fcinfo->context, dummy_value, ...) +to perform errsave() and then "return dummy_value". + +An error reported "softly" must be safe, in the sense that there is +no question about our ability to continue normal processing of the +transaction. Error conditions that should NOT be handled this way +include out-of-memory, unexpected internal errors, or anything that +cannot easily be cleaned up after. Such cases should still be thrown +with ereport, as they have been in the past. + +Considering datatype input functions as examples, typical "soft" error +conditions include input syntax errors and out-of-range values. An +input function typically detects such cases with simple if-tests and +can easily change the ensuing ereport call to an errsave or ereturn. +Because of this restriction, it's typically not necessary to pass +the ErrorSaveContext pointer down very far, as errors reported by +low-level functions are typically reasonable to consider internal. +(Another way to frame the distinction is that input functions should +report all invalid-input conditions softly, but internal problems are +hard errors.) + +Because no transaction cleanup will occur, a function that is exiting +after errsave() returns will bear responsibility for resource cleanup. +It is not necessary to be concerned about small leakages of palloc'd +memory, since the caller should be running the function in a short-lived +memory context. However, resources such as locks, open files, or buffer +pins must be closed out cleanly, as they would be in the non-error code +path. + +Conventions for callers that use the ErrorSaveContext mechanism +to trap errors are discussed with the declaration of that struct, +in nodes/miscnodes.h. + = Functions Accepting or Returning Sets ------------------------------------- diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 3c210297aa..a9519c6a8d 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -23,6 +23,7 @@ #include "lib/stringinfo.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "nodes/miscnodes.h" #include "nodes/nodeFuncs.h" #include "pgstat.h" #include "utils/acl.h" @@ -1548,6 +1549,70 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid = typioparam, int32 typmod) return result; } = +/* + * Call a previously-looked-up datatype input function, with non-exceptio= n + * handling of "soft" errors. + * + * This is basically like InputFunctionCall, but the converted Datum is + * returned into *result while the function result is true for success or + * false for failure. Also, the caller may pass an ErrorSaveContext node= . + * (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.) + * + * If escontext points to an ErrorSaveContext, any "soft" errors detected= by + * the input function will be reported by filling the escontext struct an= d + * returning false. (The caller can choose to test SOFT_ERROR_OCCURRED()= , + * but checking the function result instead is usually cheaper.) + * + * If escontext does not point to an ErrorSaveContext, errors are reporte= d + * via ereport(ERROR), so that there is no functional difference from + * InputFunctionCall; the result will always be true if control returns. + */ +bool +InputFunctionCallSafe(FmgrInfo *flinfo, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result) +{ + LOCAL_FCINFO(fcinfo, 3); + + if (str =3D=3D NULL && flinfo->fn_strict) + { + *result =3D (Datum) 0; /* just return null result */ + return true; + } + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, escontext, NULL= ); + + fcinfo->args[0].value =3D CStringGetDatum(str); + fcinfo->args[0].isnull =3D false; + fcinfo->args[1].value =3D ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull =3D false; + fcinfo->args[2].value =3D Int32GetDatum(typmod); + fcinfo->args[2].isnull =3D false; + + *result =3D FunctionCallInvoke(fcinfo); + + /* Result value is garbage, and could be null, if an error was reported = */ + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + /* Otherwise, should get null result if and only if str is NULL */ + if (str =3D=3D NULL) + { + if (!fcinfo->isnull) + elog(ERROR, "input function %u returned non-NULL", + flinfo->fn_oid); + } + else + { + if (fcinfo->isnull) + elog(ERROR, "input function %u returned NULL", + flinfo->fn_oid); + } + + return true; +} + /* * Call a previously-looked-up datatype output function. * diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 380a82b9de..b7832d0aa2 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -700,6 +700,10 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid= collation, /* Special cases for convenient invocation of datatype I/O functions. */ extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod); +extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result); extern Datum OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod); extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val); diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build index e63881086e..f0e60935b6 100644 --- a/src/include/nodes/meson.build +++ b/src/include/nodes/meson.build @@ -16,6 +16,7 @@ node_support_input_i =3D [ 'nodes/bitmapset.h', 'nodes/extensible.h', 'nodes/lockoptions.h', + 'nodes/miscnodes.h', 'nodes/replnodes.h', 'nodes/supportnodes.h', 'nodes/value.h', diff --git a/src/include/nodes/miscnodes.h b/src/include/nodes/miscnodes.h new file mode 100644 index 0000000000..b50ee60352 --- /dev/null +++ b/src/include/nodes/miscnodes.h @@ -0,0 +1,56 @@ +/*-----------------------------------------------------------------------= -- + * + * miscnodes.h + * Definitions for hard-to-classify node types. + * + * Node types declared here are not part of parse trees, plan trees, + * or execution state trees. We only assign them NodeTag values because + * IsA() tests provide a convenient way to disambiguate what kind of + * structure is being passed through assorted APIs, such as function + * "context" pointers. + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/nodes/miscnodes.h + * + *-----------------------------------------------------------------------= -- + */ +#ifndef MISCNODES_H +#define MISCNODES_H + +#include "nodes/nodes.h" + +/* + * ErrorSaveContext - + * function call context node for handling of "soft" errors + * + * A caller wishing to trap soft errors must initialize a struct like thi= s + * with all fields zero/NULL except for the NodeTag. Optionally, set + * details_wanted =3D true if more than the bare knowledge that a soft er= ror + * occurred is required. The struct is then passed to a SQL-callable fun= ction + * via the FunctionCallInfo.context field; or below the level of SQL call= s, + * it could be passed to a subroutine directly. + * + * After calling code that might report an error this way, check + * error_occurred to see if an error happened. If so, and if details_wan= ted + * is true, error_data has been filled with error details (stored in the + * callee's memory context!). FreeErrorData() can be called to release + * error_data, although that step is typically not necessary if the calle= d + * code was run in a short-lived context. + */ +typedef struct ErrorSaveContext +{ + NodeTag type; + bool error_occurred; /* set to true if we detect a soft error */ + bool details_wanted; /* does caller want more info than that? */ + ErrorData *error_data; /* details of error, if so */ +} ErrorSaveContext; + +/* Often-useful macro for checking if a soft error was reported */ +#define SOFT_ERROR_OCCURRED(escontext) \ + ((escontext) !=3D NULL && IsA(escontext, ErrorSaveContext) && \ + ((ErrorSaveContext *) (escontext))->error_occurred) + +#endif /* MISCNODES_H */ diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index f107a818e8..8025dce335 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -18,6 +18,10 @@ = #include "lib/stringinfo.h" = +/* We cannot include nodes.h yet, so forward-declare struct Node */ +struct Node; + + /* Error level codes */ #define DEBUG5 10 /* Debugging messages, in categories of * decreasing detail. */ @@ -235,6 +239,63 @@ extern int getinternalerrposition(void); ereport(elevel, errmsg_internal(__VA_ARGS__)) = = +/*---------- + * Support for reporting "soft" errors that don't require a full transact= ion + * abort to clean up. This is to be used in this way: + * errsave(context, + * errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + * errmsg("invalid input syntax for type %s: \"%s\"", + * "boolean", in_str), + * ... other errxxx() fields as needed ...); + * + * "context" is a node pointer or NULL, and the remaining auxiliary calls + * provide the same error details as in ereport(). If context is not a + * pointer to an ErrorSaveContext node, then errsave(context, ...) + * behaves identically to ereport(ERROR, ...). If context is a pointer + * to an ErrorSaveContext node, then the information provided by the + * auxiliary calls is stored in the context node and control returns + * normally. The caller of errsave() must then do any required cleanup + * and return control back to its caller. That caller must check the + * ErrorSaveContext node to see whether an error occurred before + * it can trust the function's result to be meaningful. + * + * errsave_domain() allows a message domain to be specified; it is + * precisely analogous to ereport_domain(). + *---------- + */ +#define errsave_domain(context, domain, ...) \ + do { \ + struct Node *context_ =3D (context); \ + pg_prevent_errno_in_scope(); \ + if (errsave_start(context_, domain)) \ + __VA_ARGS__, errsave_finish(context_, __FILE__, __LINE__, __func__); \ + } while(0) + +#define errsave(context, ...) \ + errsave_domain(context, TEXTDOMAIN, __VA_ARGS__) + +/* + * "ereturn(context, dummy_value, ...);" is exactly the same as + * "errsave(context, ...); return dummy_value;". This saves a bit + * of typing in the common case where a function has no cleanup + * actions to take after reporting a soft error. "dummy_value" + * can be empty if the function returns void. + */ +#define ereturn_domain(context, dummy_value, domain, ...) \ + do { \ + errsave_domain(context, domain, __VA_ARGS__); \ + return dummy_value; \ + } while(0) + +#define ereturn(context, dummy_value, ...) \ + ereturn_domain(context, dummy_value, TEXTDOMAIN, __VA_ARGS__) + +extern bool errsave_start(struct Node *context, const char *domain); +extern void errsave_finish(struct Node *context, + const char *filename, int lineno, + const char *funcname); + + /* Support for constructing error strings separately from ereport() calls= */ = extern void pre_format_elog_string(int errnumber, const char *domain); ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v5-0002-add-test-scaffolding.patch"; charset="us-ascii" Content-ID: <512546.1670540222.3@sss.pgh.pa.us> Content-Description: v5-0002-add-test-scaffolding.patch Content-Transfer-Encoding: quoted-printable diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e57ffce971..ad31fdb737 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -24683,6 +24683,107 @@ SELECT collation for ('foo' COLLATE "de_DE"); = = + + Data Validity Checking Functions + + + The functions shown in + can be helpful for checking validity of proposed input data. + + + + Data Validity Checking Functions + + + + + Function + + + Description + + + Example(s) + + + + + + + + + pg_input_is_valid + + pg_input_is_valid ( + string text, + type text + ) + boolean + + + Tests whether the given string is valid + input for the specified data type, returning true or false. + + + This function will only work as desired if the data type's input + function has been updated to report invalid input as + a soft error. Otherwise, invalid input will abort + the transaction, just as if the string had been cast to the type + directly. + + + pg_input_is_valid('42', 'integer') + t + + + pg_input_is_valid('42000000000', 'integer') + f + + + pg_input_is_valid('1234.567', 'numeric(7,4)') + f + + + + + + pg_input_error_message + + pg_input_error_message ( + string text, + type text + ) + text + + + Tests whether the given string is valid + input for the specified data type; if not, return the error + message that would have been thrown. If the input is valid, the + result is NULL. The inputs are the same as + for pg_input_is_valid. + + + This function will only work as desired if the data type's input + function has been updated to report invalid input as + a soft error. Otherwise, invalid input will abort + the transaction, just as if the string had been cast to the type + directly. + + + pg_input_error_message('42000000000', 'integer') + value "42000000000" is out of range for type intege= r + + + pg_input_error_message('1234.567', 'numeric(7,4)') + numeric field overflow + + + + +
+ +
+ Transaction ID and Snapshot Information Functions = diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index 9c13251231..20a464fb59 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -32,6 +32,8 @@ #include "common/keywords.h" #include "funcapi.h" #include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "parser/parse_type.h" #include "parser/scansup.h" #include "pgstat.h" #include "postmaster/syslogger.h" @@ -45,6 +47,23 @@ #include "utils/ruleutils.h" #include "utils/timestamp.h" = + +/* + * structure to cache metadata needed in pg_input_is_valid_common + */ +typedef struct BasicIOData +{ + Oid typoid; + Oid typiofunc; + Oid typioparam; + FmgrInfo inputproc; +} BasicIOData; + +static bool pg_input_is_valid_common(FunctionCallInfo fcinfo, + text *txt, text *typname, + ErrorSaveContext *escontext); + + /* * Common subroutine for num_nulls() and num_nonnulls(). * Returns true if successful, false if function should return NULL. @@ -640,6 +659,104 @@ pg_column_is_updatable(PG_FUNCTION_ARGS) } = = +/* + * pg_input_is_valid - test whether string is valid input for datatype. + * + * Returns true if OK, false if not. + * + * This will only work usefully if the datatype's input function has been + * updated to return "soft" errors via errsave/ereturn. + */ +Datum +pg_input_is_valid(PG_FUNCTION_ARGS) +{ + text *txt =3D PG_GETARG_TEXT_PP(0); + text *typname =3D PG_GETARG_TEXT_PP(1); + ErrorSaveContext escontext =3D {T_ErrorSaveContext}; + + PG_RETURN_BOOL(pg_input_is_valid_common(fcinfo, txt, typname, + &escontext)); +} + +/* + * pg_input_error_message - test whether string is valid input for dataty= pe. + * + * Returns NULL if OK, else the primary message string from the error. + * + * This will only work usefully if the datatype's input function has been + * updated to return "soft" errors via errsave/ereturn. + */ +Datum +pg_input_error_message(PG_FUNCTION_ARGS) +{ + text *txt =3D PG_GETARG_TEXT_PP(0); + text *typname =3D PG_GETARG_TEXT_PP(1); + ErrorSaveContext escontext =3D {T_ErrorSaveContext}; + + /* Enable details_wanted */ + escontext.details_wanted =3D true; + + if (pg_input_is_valid_common(fcinfo, txt, typname, + &escontext)) + PG_RETURN_NULL(); + + Assert(escontext.error_occurred); + Assert(escontext.error_data !=3D NULL); + Assert(escontext.error_data->message !=3D NULL); + + PG_RETURN_TEXT_P(cstring_to_text(escontext.error_data->message)); +} + +/* Common subroutine for the above */ +static bool +pg_input_is_valid_common(FunctionCallInfo fcinfo, + text *txt, text *typname, + ErrorSaveContext *escontext) +{ + char *str =3D text_to_cstring(txt); + char *typnamestr =3D text_to_cstring(typname); + Oid typoid; + int32 typmod; + BasicIOData *my_extra; + Datum converted; + + /* Parse type-name argument to obtain type OID and encoded typmod. */ + parseTypeString(typnamestr, &typoid, &typmod, false); + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the data type doesn't change underneath us. + */ + my_extra =3D (BasicIOData *) fcinfo->flinfo->fn_extra; + if (my_extra =3D=3D NULL) + { + fcinfo->flinfo->fn_extra =3D + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(BasicIOData)); + my_extra =3D (BasicIOData *) fcinfo->flinfo->fn_extra; + my_extra->typoid =3D InvalidOid; + } + + if (my_extra->typoid !=3D typoid) + { + getTypeInputInfo(typoid, + &my_extra->typiofunc, + &my_extra->typioparam); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->inputproc, + fcinfo->flinfo->fn_mcxt); + my_extra->typoid =3D typoid; + } + + /* Now we can try to perform the conversion. */ + return InputFunctionCallSafe(&my_extra->inputproc, + str, + my_extra->typioparam, + typmod, + (Node *) escontext, + &converted); +} + + /* * Is character a valid identifier start? * Must match scan.l's {ident_start} character class. diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc= .dat index f9301b2627..719599649a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7060,6 +7060,14 @@ prorettype =3D> 'regnamespace', proargtypes =3D> 'text', prosrc =3D> 'to_regnamespace' }, = +{ oid =3D> '8050', descr =3D> 'test whether string is valid input for dat= a type', + proname =3D> 'pg_input_is_valid', provolatile =3D> 's', prorettype =3D>= 'bool', + proargtypes =3D> 'text text', prosrc =3D> 'pg_input_is_valid' }, +{ oid =3D> '8051', + descr =3D> 'get error message if string is not valid input for data typ= e', + proname =3D> 'pg_input_error_message', provolatile =3D> 's', prorettype= =3D> 'text', + proargtypes =3D> 'text text', prosrc =3D> 'pg_input_error_message' }, + { oid =3D> '1268', descr =3D> 'parse qualified identifier to array of identifiers', proname =3D> 'parse_ident', prorettype =3D> '_text', proargtypes =3D> '= text bool', diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/= expected/create_type.out index 0dfc88c1c8..7383fcdbb1 100644 --- a/src/test/regress/expected/create_type.out +++ b/src/test/regress/expected/create_type.out @@ -249,6 +249,31 @@ select format_type('bpchar'::regtype, -1); bpchar (1 row) = +-- Test non-error-throwing APIs using widget, which still throws errors +SELECT pg_input_is_valid('(1,2,3)', 'widget'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('(1,2)', 'widget'); -- hard error expected +ERROR: invalid input syntax for type widget: "(1,2)" +SELECT pg_input_is_valid('{"(1,2,3)"}', 'widget[]'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('{"(1,2)"}', 'widget[]'); -- hard error expecte= d +ERROR: invalid input syntax for type widget: "(1,2)" +SELECT pg_input_is_valid('("(1,2,3)")', 'mytab'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('("(1,2)")', 'mytab'); -- hard error expected +ERROR: invalid input syntax for type widget: "(1,2)" -- Test creation of an operator over a user-defined type CREATE FUNCTION pt_in_widget(point, widget) RETURNS bool diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 548afb4438..2977045cc7 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -183,6 +183,11 @@ widget_in(PG_FUNCTION_ARGS) coord[i++] =3D p + 1; } = + /* + * Note: DON'T convert this error to "soft" style (errsave/ereturn). We + * want this data type to stay permanently in the hard-error world so th= at + * it can be used for testing that such cases still work reasonably. + */ if (i < NARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/c= reate_type.sql index c6fc4f9029..c25018029c 100644 --- a/src/test/regress/sql/create_type.sql +++ b/src/test/regress/sql/create_type.sql @@ -192,6 +192,14 @@ select format_type('bpchar'::regtype, null); -- this behavior difference is intentional select format_type('bpchar'::regtype, -1); = +-- Test non-error-throwing APIs using widget, which still throws errors +SELECT pg_input_is_valid('(1,2,3)', 'widget'); +SELECT pg_input_is_valid('(1,2)', 'widget'); -- hard error expected +SELECT pg_input_is_valid('{"(1,2,3)"}', 'widget[]'); +SELECT pg_input_is_valid('{"(1,2)"}', 'widget[]'); -- hard error expecte= d +SELECT pg_input_is_valid('("(1,2,3)")', 'mytab'); +SELECT pg_input_is_valid('("(1,2)")', 'mytab'); -- hard error expected + -- Test creation of an operator over a user-defined type = CREATE FUNCTION pt_in_widget(point, widget) ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v5-0003-convert-a-few-data-types.patch"; charset="us-ascii" Content-ID: <512546.1670540222.4@sss.pgh.pa.us> Content-Description: v5-0003-convert-a-few-data-types.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/ar= rayfuncs.c index 495e449a9e..c011ebdfd9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -90,14 +90,15 @@ typedef struct ArrayIteratorData } ArrayIteratorData; = static bool array_isspace(char ch); -static int ArrayCount(const char *str, int *dim, char typdelim); -static void ReadArrayStr(char *arrayStr, const char *origStr, +static int ArrayCount(const char *str, int *dim, char typdelim, + Node *escontext); +static bool ReadArrayStr(char *arrayStr, const char *origStr, int nitems, int ndim, int *dim, FmgrInfo *inputproc, Oid typioparam, int32 typmod, char typdelim, int typlen, bool typbyval, char typalign, Datum *values, bool *nulls, - bool *hasnulls, int32 *nbytes); + bool *hasnulls, int32 *nbytes, Node *escontext); static void ReadArrayBinary(StringInfo buf, int nitems, FmgrInfo *receiveproc, Oid typioparam, int32 typmod, int typlen, bool typbyval, char typalign, @@ -177,6 +178,7 @@ array_in(PG_FUNCTION_ARGS) Oid element_type =3D PG_GETARG_OID(1); /* type of an array * element */ int32 typmod =3D PG_GETARG_INT32(2); /* typmod for array elements */ + Node *escontext =3D fcinfo->context; int typlen; bool typbyval; char typalign; @@ -258,7 +260,7 @@ array_in(PG_FUNCTION_ARGS) break; /* no more dimension items */ p++; if (ndim >=3D MAXDIM) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed= (%d)", ndim + 1, MAXDIM))); @@ -266,7 +268,7 @@ array_in(PG_FUNCTION_ARGS) for (q =3D p; isdigit((unsigned char) *q) || (*q =3D=3D '-') || (*q =3D= =3D '+'); q++) /* skip */ ; if (q =3D=3D p) /* no digits? */ - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("\"[\" must introduce explicitly-specified array dimensio= ns."))); @@ -280,7 +282,7 @@ array_in(PG_FUNCTION_ARGS) for (q =3D p; isdigit((unsigned char) *q) || (*q =3D=3D '-') || (*q =3D= =3D '+'); q++) /* skip */ ; if (q =3D=3D p) /* no digits? */ - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Missing array dimension value."))); @@ -291,7 +293,7 @@ array_in(PG_FUNCTION_ARGS) lBound[ndim] =3D 1; } if (*q !=3D ']') - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Missing \"%s\" after array dimensions.", @@ -301,7 +303,7 @@ array_in(PG_FUNCTION_ARGS) ub =3D atoi(p); p =3D q + 1; if (ub < lBound[ndim]) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("upper bound cannot be less than lower bound"))); = @@ -313,11 +315,13 @@ array_in(PG_FUNCTION_ARGS) { /* No array dimensions, so intuit dimensions from brace structure */ if (*p !=3D '{') - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Array value must start with \"{\" or dimension informati= on."))); - ndim =3D ArrayCount(p, dim, typdelim); + ndim =3D ArrayCount(p, dim, typdelim, escontext); + if (ndim < 0) + PG_RETURN_NULL(); for (i =3D 0; i < ndim; i++) lBound[i] =3D 1; } @@ -328,7 +332,7 @@ array_in(PG_FUNCTION_ARGS) = /* If array dimensions are given, expect '=3D' operator */ if (strncmp(p, ASSGN, strlen(ASSGN)) !=3D 0) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Missing \"%s\" after array dimensions.", @@ -342,20 +346,22 @@ array_in(PG_FUNCTION_ARGS) * were given */ if (*p !=3D '{') - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Array contents must start with \"{\"."))); - ndim_braces =3D ArrayCount(p, dim_braces, typdelim); + ndim_braces =3D ArrayCount(p, dim_braces, typdelim, escontext); + if (ndim_braces < 0) + PG_RETURN_NULL(); if (ndim_braces !=3D ndim) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Specified array dimensions do not match array contents."= ))); for (i =3D 0; i < ndim; ++i) { if (dim[i] !=3D dim_braces[i]) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", string), errdetail("Specified array dimensions do not match array contents.= "))); @@ -372,8 +378,11 @@ array_in(PG_FUNCTION_ARGS) #endif = /* This checks for overflow of the array dimensions */ - nitems =3D ArrayGetNItems(ndim, dim); - ArrayCheckBounds(ndim, dim, lBound); + nitems =3D ArrayGetNItemsSafe(ndim, dim, escontext); + if (nitems < 0) + PG_RETURN_NULL(); + if (!ArrayCheckBoundsSafe(ndim, dim, lBound, escontext)) + PG_RETURN_NULL(); = /* Empty array? */ if (nitems =3D=3D 0) @@ -381,13 +390,14 @@ array_in(PG_FUNCTION_ARGS) = dataPtr =3D (Datum *) palloc(nitems * sizeof(Datum)); nullsPtr =3D (bool *) palloc(nitems * sizeof(bool)); - ReadArrayStr(p, string, - nitems, ndim, dim, - &my_extra->proc, typioparam, typmod, - typdelim, - typlen, typbyval, typalign, - dataPtr, nullsPtr, - &hasnulls, &nbytes); + if (!ReadArrayStr(p, string, + nitems, ndim, dim, + &my_extra->proc, typioparam, typmod, + typdelim, + typlen, typbyval, typalign, + dataPtr, nullsPtr, + &hasnulls, &nbytes, escontext)) + PG_RETURN_NULL(); if (hasnulls) { dataoffset =3D ARR_OVERHEAD_WITHNULLS(ndim, nitems); @@ -451,9 +461,12 @@ array_isspace(char ch) * * Returns number of dimensions as function result. The axis lengths are * returned in dim[], which must be of size MAXDIM. + * + * If we detect an error, fill *escontext with error details and return -= 1 + * (unless escontext isn't provided, in which case errors will be thrown)= . */ static int -ArrayCount(const char *str, int *dim, char typdelim) +ArrayCount(const char *str, int *dim, char typdelim, Node *escontext) { int nest_level =3D 0, i; @@ -488,11 +501,10 @@ ArrayCount(const char *str, int *dim, char typdelim) { case '\0': /* Signal a premature end of the string */ - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected end of input."))); - break; case '\\': = /* @@ -504,7 +516,7 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state !=3D ARRAY_ELEM_STARTED && parse_state !=3D ARRAY_QUOTED_ELEM_STARTED && parse_state !=3D ARRAY_ELEM_DELIMITED) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected \"%c\" character.", @@ -515,7 +527,7 @@ ArrayCount(const char *str, int *dim, char typdelim) if (*(ptr + 1)) ptr++; else - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected end of input."))); @@ -530,7 +542,7 @@ ArrayCount(const char *str, int *dim, char typdelim) if (parse_state !=3D ARRAY_LEVEL_STARTED && parse_state !=3D ARRAY_QUOTED_ELEM_STARTED && parse_state !=3D ARRAY_ELEM_DELIMITED) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected array element."))); @@ -551,14 +563,14 @@ ArrayCount(const char *str, int *dim, char typdelim) if (parse_state !=3D ARRAY_NO_LEVEL && parse_state !=3D ARRAY_LEVEL_STARTED && parse_state !=3D ARRAY_LEVEL_DELIMITED) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected \"%c\" character.", '{'))); parse_state =3D ARRAY_LEVEL_STARTED; if (nest_level >=3D MAXDIM) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum all= owed (%d)", nest_level + 1, MAXDIM))); @@ -581,14 +593,14 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state !=3D ARRAY_QUOTED_ELEM_COMPLETED && parse_state !=3D ARRAY_LEVEL_COMPLETED && !(nest_level =3D=3D 1 && parse_state =3D=3D ARRAY_LEVEL_STARTED)) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected \"%c\" character.", '}'))); parse_state =3D ARRAY_LEVEL_COMPLETED; if (nest_level =3D=3D 0) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unmatched \"%c\" character.", '}'))); @@ -596,7 +608,7 @@ ArrayCount(const char *str, int *dim, char typdelim) = if (nelems_last[nest_level] !=3D 0 && nelems[nest_level] !=3D nelems_last[nest_level]) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Multidimensional arrays must have " @@ -630,7 +642,7 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state !=3D ARRAY_ELEM_COMPLETED && parse_state !=3D ARRAY_QUOTED_ELEM_COMPLETED && parse_state !=3D ARRAY_LEVEL_COMPLETED) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected \"%c\" character.", @@ -653,7 +665,7 @@ ArrayCount(const char *str, int *dim, char typdelim) if (parse_state !=3D ARRAY_LEVEL_STARTED && parse_state !=3D ARRAY_ELEM_STARTED && parse_state !=3D ARRAY_ELEM_DELIMITED) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Unexpected array element."))); @@ -673,7 +685,7 @@ ArrayCount(const char *str, int *dim, char typdelim) while (*ptr) { if (!array_isspace(*ptr++)) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", str), errdetail("Junk after closing right brace."))); @@ -713,11 +725,16 @@ ArrayCount(const char *str, int *dim, char typdelim) * *hasnulls: set true iff there are any null elements. * *nbytes: set to total size of data area needed (including alignment * padding but not including array header overhead). + * *escontext: if this points to an ErrorSaveContext, details of + * any error are reported there. + * + * Result: + * true for success, false for failure (if escontext is provided). * * Note that values[] and nulls[] are allocated by the caller, and must h= ave * nitems elements. */ -static void +static bool ReadArrayStr(char *arrayStr, const char *origStr, int nitems, @@ -733,7 +750,8 @@ ReadArrayStr(char *arrayStr, Datum *values, bool *nulls, bool *hasnulls, - int32 *nbytes) + int32 *nbytes, + Node *escontext) { int i, nest_level =3D 0; @@ -784,7 +802,7 @@ ReadArrayStr(char *arrayStr, { case '\0': /* Signal a premature end of the string */ - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", origStr))); @@ -793,7 +811,7 @@ ReadArrayStr(char *arrayStr, /* Skip backslash, copy next character as-is. */ srcptr++; if (*srcptr =3D=3D '\0') - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", origStr))); @@ -823,7 +841,7 @@ ReadArrayStr(char *arrayStr, if (!in_quotes) { if (nest_level >=3D ndim) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", origStr))); @@ -838,7 +856,7 @@ ReadArrayStr(char *arrayStr, if (!in_quotes) { if (nest_level =3D=3D 0) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", origStr))); @@ -891,7 +909,7 @@ ReadArrayStr(char *arrayStr, *dstendptr =3D '\0'; = if (i < 0 || i >=3D nitems) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed array literal: \"%s\"", origStr))); @@ -900,14 +918,20 @@ ReadArrayStr(char *arrayStr, pg_strcasecmp(itemstart, "NULL") =3D=3D 0) { /* it's a NULL item */ - values[i] =3D InputFunctionCall(inputproc, NULL, - typioparam, typmod); + if (!InputFunctionCallSafe(inputproc, NULL, + typioparam, typmod, + escontext, + &values[i])) + return false; nulls[i] =3D true; } else { - values[i] =3D InputFunctionCall(inputproc, itemstart, - typioparam, typmod); + if (!InputFunctionCallSafe(inputproc, itemstart, + typioparam, typmod, + escontext, + &values[i])) + return false; nulls[i] =3D false; } } @@ -930,7 +954,7 @@ ReadArrayStr(char *arrayStr, totbytes =3D att_align_nominal(totbytes, typalign); /* check for overflow of total request */ if (!AllocSizeIsValid(totbytes)) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("array size exceeds the maximum allowed (%d)", (int) MaxAllocSize))); @@ -938,6 +962,7 @@ ReadArrayStr(char *arrayStr, } *hasnulls =3D hasnull; *nbytes =3D totbytes; + return true; } = = diff --git a/src/backend/utils/adt/arrayutils.c b/src/backend/utils/adt/ar= rayutils.c index 051169a149..3821f6637b 100644 --- a/src/backend/utils/adt/arrayutils.c +++ b/src/backend/utils/adt/arrayutils.c @@ -74,6 +74,16 @@ ArrayGetOffset0(int n, const int *tup, const int *scale= ) */ int ArrayGetNItems(int ndim, const int *dims) +{ + return ArrayGetNItemsSafe(ndim, dims, NULL); +} + +/* + * This entry point can return the error into an ErrorSaveContext + * instead of throwing an exception. -1 is returned after an error. + */ +int +ArrayGetNItemsSafe(int ndim, const int *dims, struct Node *escontext) { int32 ret; int i; @@ -89,7 +99,7 @@ ArrayGetNItems(int ndim, const int *dims) = /* A negative dimension implies that UB-LB overflowed ... */ if (dims[i] < 0) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("array size exceeds the maximum allowed (%d)", (int) MaxArraySize))); @@ -98,14 +108,14 @@ ArrayGetNItems(int ndim, const int *dims) = ret =3D (int32) prod; if ((int64) ret !=3D prod) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("array size exceeds the maximum allowed (%d)", (int) MaxArraySize))); } Assert(ret >=3D 0); if ((Size) ret > MaxArraySize) - ereport(ERROR, + ereturn(escontext, -1, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("array size exceeds the maximum allowed (%d)", (int) MaxArraySize))); @@ -126,6 +136,17 @@ ArrayGetNItems(int ndim, const int *dims) */ void ArrayCheckBounds(int ndim, const int *dims, const int *lb) +{ + (void) ArrayCheckBoundsSafe(ndim, dims, lb, NULL); +} + +/* + * This entry point can return the error into an ErrorSaveContext + * instead of throwing an exception. + */ +bool +ArrayCheckBoundsSafe(int ndim, const int *dims, const int *lb, + struct Node *escontext) { int i; = @@ -135,11 +156,13 @@ ArrayCheckBounds(int ndim, const int *dims, const in= t *lb) int32 sum PG_USED_FOR_ASSERTS_ONLY; = if (pg_add_s32_overflow(dims[i], lb[i], &sum)) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("array lower bound is too large: %d", lb[i]))); } + + return true; } = /* diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c index cd7335287f..e291672ae4 100644 --- a/src/backend/utils/adt/bool.c +++ b/src/backend/utils/adt/bool.c @@ -148,13 +148,10 @@ boolin(PG_FUNCTION_ARGS) if (parse_bool_with_len(str, len, &result)) PG_RETURN_BOOL(result); = - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "boolean", in_str))); - - /* not reached */ - PG_RETURN_BOOL(false); } = /* diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 42ddae99ef..e1837bee71 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -291,7 +291,7 @@ int4in(PG_FUNCTION_ARGS) { char *num =3D PG_GETARG_CSTRING(0); = - PG_RETURN_INT32(pg_strtoint32(num)); + PG_RETURN_INT32(pg_strtoint32_safe(num, fcinfo->context)); } = /* diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numu= tils.c index a64422c8d0..0de0bed0e8 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -166,8 +166,11 @@ invalid_syntax: /* * Convert input string to a signed 32 bit integer. * - * Allows any number of leading or trailing whitespace characters. Will t= hrow - * ereport() upon bad input format or overflow. + * Allows any number of leading or trailing whitespace characters. + * + * pg_strtoint32() will throw ereport() upon bad input format or overflow= ; + * while pg_strtoint32_safe() instead returns such complaints in *esconte= xt, + * if it's an ErrorSaveContext. * * NB: Accumulate input as an unsigned number, to deal with two's complem= ent * representation of the most negative number, which can't be represented= as a @@ -175,6 +178,12 @@ invalid_syntax: */ int32 pg_strtoint32(const char *s) +{ + return pg_strtoint32_safe(s, NULL); +} + +int32 +pg_strtoint32_safe(const char *s, Node *escontext) { const char *ptr =3D s; uint32 tmp =3D 0; @@ -227,18 +236,16 @@ pg_strtoint32(const char *s) return (int32) tmp; = out_of_range: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value \"%s\" is out of range for type %s", s, "integer"))); = invalid_syntax: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "integer", s))); - - return 0; /* keep compiler quiet */ } = /* diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowt= ypes.c index db843a0fbf..bdafcff02d 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -77,6 +77,7 @@ record_in(PG_FUNCTION_ARGS) char *string =3D PG_GETARG_CSTRING(0); Oid tupType =3D PG_GETARG_OID(1); int32 tupTypmod =3D PG_GETARG_INT32(2); + Node *escontext =3D fcinfo->context; HeapTupleHeader result; TupleDesc tupdesc; HeapTuple tuple; @@ -100,7 +101,7 @@ record_in(PG_FUNCTION_ARGS) * supply a valid typmod, and then we can do something useful for RECORD= . */ if (tupType =3D=3D RECORDOID && tupTypmod < 0) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); = @@ -152,10 +153,13 @@ record_in(PG_FUNCTION_ARGS) while (*ptr && isspace((unsigned char) *ptr)) ptr++; if (*ptr++ !=3D '(') - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Missing left parenthesis."))); + goto fail; + } = initStringInfo(&buf); = @@ -181,10 +185,13 @@ record_in(PG_FUNCTION_ARGS) ptr++; else /* *ptr must be ')' */ - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Too few columns."))); + goto fail; + } } = /* Check for null: completely empty input means null */ @@ -204,19 +211,25 @@ record_in(PG_FUNCTION_ARGS) char ch =3D *ptr++; = if (ch =3D=3D '\0') - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Unexpected end of input."))); + goto fail; + } if (ch =3D=3D '\\') { if (*ptr =3D=3D '\0') - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Unexpected end of input."))); + goto fail; + } appendStringInfoChar(&buf, *ptr++); } else if (ch =3D=3D '"') @@ -252,10 +265,13 @@ record_in(PG_FUNCTION_ARGS) column_info->column_type =3D column_type; } = - values[i] =3D InputFunctionCall(&column_info->proc, - column_data, - column_info->typioparam, - att->atttypmod); + if (!InputFunctionCallSafe(&column_info->proc, + column_data, + column_info->typioparam, + att->atttypmod, + escontext, + &values[i])) + goto fail; = /* * Prep for next column @@ -264,18 +280,24 @@ record_in(PG_FUNCTION_ARGS) } = if (*ptr++ !=3D ')') - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Too many columns."))); + goto fail; + } /* Allow trailing whitespace */ while (*ptr && isspace((unsigned char) *ptr)) ptr++; if (*ptr) - ereport(ERROR, + { + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Junk after right parenthesis."))); + goto fail; + } = tuple =3D heap_form_tuple(tupdesc, values, nulls); = @@ -294,6 +316,11 @@ record_in(PG_FUNCTION_ARGS) ReleaseTupleDesc(tupdesc); = PG_RETURN_HEAPTUPLEHEADER(result); + + /* exit here once we've done lookup_rowtype_tupdesc */ +fail: + ReleaseTupleDesc(tupdesc); + PG_RETURN_NULL(); } = /* diff --git a/src/include/utils/array.h b/src/include/utils/array.h index 2f794d1168..3f6319aed5 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -447,7 +447,11 @@ extern void array_free_iterator(ArrayIterator iterato= r); extern int ArrayGetOffset(int n, const int *dim, const int *lb, const int= *indx); extern int ArrayGetOffset0(int n, const int *tup, const int *scale); extern int ArrayGetNItems(int ndim, const int *dims); +extern int ArrayGetNItemsSafe(int ndim, const int *dims, + struct Node *escontext); extern void ArrayCheckBounds(int ndim, const int *dims, const int *lb); +extern bool ArrayCheckBoundsSafe(int ndim, const int *dims, const int *lb= , + struct Node *escontext); extern void mda_get_range(int n, int *span, const int *st, const int *end= p); extern void mda_get_prod(int n, const int *range, int *prod); extern void mda_get_offset_values(int n, int *dist, const int *prod, cons= t int *span); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 81631f1645..fbfd8375e3 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -45,6 +45,7 @@ extern int namestrcmp(Name name, const char *str); /* numutils.c */ extern int16 pg_strtoint16(const char *s); extern int32 pg_strtoint32(const char *s); +extern int32 pg_strtoint32_safe(const char *s, Node *escontext); extern int64 pg_strtoint64(const char *s); extern int pg_itoa(int16 i, char *a); extern int pg_ultoa_n(uint32 value, char *a); diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expec= ted/arrays.out index 97920f38c2..a2f9d7ed16 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -182,6 +182,31 @@ SELECT a,b,c FROM arrtest; [4:4]=3D{NULL} | {3,4} | {foo,new_word} (3 rows) = +-- test non-error-throwing API +SELECT pg_input_is_valid('{1,2,3}', 'integer[]'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('{1,2', 'integer[]'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('{1,zed}', 'integer[]'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('{1,zed}', 'integer[]'); + pg_input_error_message = +---------------------------------------------- + invalid input syntax for type integer: "zed" +(1 row) + -- test mixed slice/scalar subscripting select '{{1,2,3},{4,5,6},{7,8,9}}'::int[]; int4 = diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expe= cted/boolean.out index 4728fe2dfd..977124b20b 100644 --- a/src/test/regress/expected/boolean.out +++ b/src/test/regress/expected/boolean.out @@ -142,6 +142,25 @@ SELECT bool '' AS error; ERROR: invalid input syntax for type boolean: "" LINE 1: SELECT bool '' AS error; ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('true', 'bool'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('asdf', 'bool'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('junk', 'bool'); + pg_input_error_message = +----------------------------------------------- + invalid input syntax for type boolean: "junk" +(1 row) + -- and, or, not in qualifications SELECT bool 't' or bool 'f' AS true; true = diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expecte= d/int4.out index fbcc0e8d9e..b98007bd7a 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -45,6 +45,31 @@ SELECT * FROM INT4_TBL; -2147483647 (5 rows) = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int4'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('asdf', 'int4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('1000000000000', 'int4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1000000000000', 'int4'); + pg_input_error_message = +-------------------------------------------------------- + value "1000000000000" is out of range for type integer +(1 row) + SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0'; f1 = ------------- diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/exp= ected/rowtypes.out index a4cc2d8c12..1bcd2b499c 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -69,6 +69,32 @@ ERROR: malformed record literal: "(Joe,Blow) /" LINE 1: select '(Joe,Blow) /'::fullname; ^ DETAIL: Junk after right parenthesis. +-- test non-error-throwing API +create type twoints as (r integer, i integer); +SELECT pg_input_is_valid('(1,2)', 'twoints'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('(1,2', 'twoints'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('(1,zed)', 'twoints'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('(1,zed)', 'twoints'); + pg_input_error_message = +---------------------------------------------- + invalid input syntax for type integer: "zed" +(1 row) + create temp table quadtable(f1 int, q quad); insert into quadtable values (1, ((3.3,4.4),(5.5,6.6))); insert into quadtable values (2, ((null,4.4),(5.5,6.6))); diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays= .sql index 791af5c0ce..38e8dd440b 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -113,6 +113,12 @@ SELECT a FROM arrtest WHERE a[2] IS NULL; DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL; SELECT a,b,c FROM arrtest; = +-- test non-error-throwing API +SELECT pg_input_is_valid('{1,2,3}', 'integer[]'); +SELECT pg_input_is_valid('{1,2', 'integer[]'); +SELECT pg_input_is_valid('{1,zed}', 'integer[]'); +SELECT pg_input_error_message('{1,zed}', 'integer[]'); + -- test mixed slice/scalar subscripting select '{{1,2,3},{4,5,6},{7,8,9}}'::int[]; select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2]; diff --git a/src/test/regress/sql/boolean.sql b/src/test/regress/sql/boole= an.sql index 4dd47aaf9d..dfaa55dd0f 100644 --- a/src/test/regress/sql/boolean.sql +++ b/src/test/regress/sql/boolean.sql @@ -62,6 +62,11 @@ SELECT bool '000' AS error; = SELECT bool '' AS error; = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('true', 'bool'); +SELECT pg_input_is_valid('asdf', 'bool'); +SELECT pg_input_error_message('junk', 'bool'); + -- and, or, not in qualifications = SELECT bool 't' or bool 'f' AS true; diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql index f19077f3da..54420818de 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -17,6 +17,12 @@ INSERT INTO INT4_TBL(f1) VALUES (''); = SELECT * FROM INT4_TBL; = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int4'); +SELECT pg_input_is_valid('asdf', 'int4'); +SELECT pg_input_is_valid('1000000000000', 'int4'); +SELECT pg_input_error_message('1000000000000', 'int4'); + SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0'; = SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int4 '0'; diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowt= ypes.sql index ad5b7e128f..4cd6a49215 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -31,6 +31,13 @@ select '[]'::fullname; -- bad select ' (Joe,Blow) '::fullname; -- ok, extra whitespace select '(Joe,Blow) /'::fullname; -- bad = +-- test non-error-throwing API +create type twoints as (r integer, i integer); +SELECT pg_input_is_valid('(1,2)', 'twoints'); +SELECT pg_input_is_valid('(1,2', 'twoints'); +SELECT pg_input_is_valid('(1,zed)', 'twoints'); +SELECT pg_input_error_message('(1,zed)', 'twoints'); + create temp table quadtable(f1 int, q quad); = insert into quadtable values (1, ((3.3,4.4),(5.5,6.6))); ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v5-0004-convert-numeric-types.patch"; charset="us-ascii" Content-ID: <512546.1670540222.5@sss.pgh.pa.us> Content-Description: v5-0004-convert-numeric-types.patch Content-Transfer-Encoding: quoted-printable diff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.y index 977dcba965..e6e361736c 100644 --- a/contrib/cube/cubeparse.y +++ b/contrib/cube/cubeparse.y @@ -190,18 +190,18 @@ write_box(int dim, char *str1, char *str2) s =3D str1; i =3D 0; if (dim > 0) - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, NULL); while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, NULL); } Assert(i =3D=3D dim); = s =3D str2; if (dim > 0) { - bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2); + bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, NULL); /* code this way to do right thing with NaN */ point &=3D (bp->x[i] =3D=3D bp->x[0]); i++; @@ -209,7 +209,7 @@ write_box(int dim, char *str1, char *str2) while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2); + bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, NULL); point &=3D (bp->x[i] =3D=3D bp->x[i - dim]); i++; } @@ -250,11 +250,11 @@ write_point_as_box(int dim, char *str) s =3D str; i =3D 0; if (dim > 0) - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, NULL); while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, NULL); } Assert(i =3D=3D dim); = diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index da97538ebe..b02a19be24 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -163,6 +163,7 @@ Datum float4in(PG_FUNCTION_ARGS) { char *num =3D PG_GETARG_CSTRING(0); + Node *escontext =3D fcinfo->context; char *orig_num; float val; char *endptr; @@ -183,7 +184,7 @@ float4in(PG_FUNCTION_ARGS) * strtod() on different platforms. */ if (*num =3D=3D '\0') - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "real", orig_num))); @@ -257,13 +258,13 @@ float4in(PG_FUNCTION_ARGS) (val >=3D HUGE_VALF || val <=3D -HUGE_VALF) #endif ) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("\"%s\" is out of range for type real", orig_num))); } else - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "real", orig_num))); @@ -275,7 +276,7 @@ float4in(PG_FUNCTION_ARGS) = /* if there is any junk left at the end of the string, bail out */ if (*endptr !=3D '\0') - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "real", orig_num))); @@ -337,52 +338,40 @@ float8in(PG_FUNCTION_ARGS) { char *num =3D PG_GETARG_CSTRING(0); = - PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num)); + PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num, + fcinfo->context)); } = -/* Convenience macro: set *have_error flag (if provided) or throw error *= / -#define RETURN_ERROR(throw_error, have_error) \ -do { \ - if (have_error) { \ - *have_error =3D true; \ - return 0.0; \ - } else { \ - throw_error; \ - } \ -} while (0) - /* - * float8in_internal_opt_error - guts of float8in() + * float8in_internal - guts of float8in() * * This is exposed for use by functions that want a reasonably * platform-independent way of inputting doubles. The behavior is - * essentially like strtod + ereport on error, but note the following + * essentially like strtod + ereturn on error, but note the following * differences: * 1. Both leading and trailing whitespace are skipped. - * 2. If endptr_p is NULL, we throw error if there's trailing junk. + * 2. If endptr_p is NULL, we report error if there's trailing junk. * Otherwise, it's up to the caller to complain about trailing junk. * 3. In event of a syntax error, the report mentions the given type_name * and prints orig_string as the input; this is meant to support use of * this function with types such as "box" and "point", where what we are * parsing here is just a substring of orig_string. * + * If escontext points to an ErrorSaveContext node, that is filled instea= d + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + * * "num" could validly be declared "const char *", but that results in an * unreasonable amount of extra casting both here and in callers, so we d= on't. - * - * When "*have_error" flag is provided, it's set instead of throwing an - * error. This is helpful when caller need to handle errors by itself. */ -double -float8in_internal_opt_error(char *num, char **endptr_p, - const char *type_name, const char *orig_string, - bool *have_error) +float8 +float8in_internal(char *num, char **endptr_p, + const char *type_name, const char *orig_string, + struct Node *escontext) { double val; char *endptr; = - if (have_error) - *have_error =3D false; - /* skip leading whitespace */ while (*num !=3D '\0' && isspace((unsigned char) *num)) num++; @@ -392,11 +381,10 @@ float8in_internal_opt_error(char *num, char **endptr= _p, * strtod() on different platforms. */ if (*num =3D=3D '\0') - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - type_name, orig_string))), - have_error); + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); = errno =3D 0; val =3D strtod(num, &endptr); @@ -469,20 +457,17 @@ float8in_internal_opt_error(char *num, char **endptr= _p, char *errnumber =3D pstrdup(num); = errnumber[endptr - num] =3D '\0'; - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("\"%s\" is out of range for type double precision", - errnumber))), - have_error); + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"%s\" is out of range for type double precision", + errnumber))); } } else - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type " - "%s: \"%s\"", - type_name, orig_string))), - have_error); + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); } = /* skip trailing whitespace */ @@ -493,27 +478,14 @@ float8in_internal_opt_error(char *num, char **endptr= _p, if (endptr_p) *endptr_p =3D endptr; else if (*endptr !=3D '\0') - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type " - "%s: \"%s\"", - type_name, orig_string))), - have_error); + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); = return val; } = -/* - * Interface to float8in_internal_opt_error() without "have_error" argume= nt. - */ -double -float8in_internal(char *num, char **endptr_p, - const char *type_name, const char *orig_string) -{ - return float8in_internal_opt_error(num, endptr_p, type_name, - orig_string, NULL); -} - = /* * float8out - converts float8 number to a string diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_o= ps.c index d78002b901..721ce6634f 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -189,7 +189,7 @@ static float8 single_decode(char *num, char **endptr_p, const char *type_name, const char *orig_string) { - return float8in_internal(num, endptr_p, type_name, orig_string); + return float8in_internal(num, endptr_p, type_name, orig_string, NULL); } /* single_decode() */ = static void @@ -212,7 +212,7 @@ pair_decode(char *str, float8 *x, float8 *y, char **en= dptr_p, if ((has_delim =3D (*str =3D=3D LDELIM))) str++; = - *x =3D float8in_internal(str, &str, type_name, orig_string); + *x =3D float8in_internal(str, &str, type_name, orig_string, NULL); = if (*str++ !=3D DELIM) ereport(ERROR, @@ -220,7 +220,7 @@ pair_decode(char *str, float8 *x, float8 *y, char **en= dptr_p, errmsg("invalid input syntax for type %s: \"%s\"", type_name, orig_string))); = - *y =3D float8in_internal(str, &str, type_name, orig_string); + *y =3D float8in_internal(str, &str, type_name, orig_string, NULL); = if (has_delim) { diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index e1837bee71..8de38abd11 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -64,7 +64,7 @@ int2in(PG_FUNCTION_ARGS) { char *num =3D PG_GETARG_CSTRING(0); = - PG_RETURN_INT16(pg_strtoint16(num)); + PG_RETURN_INT16(pg_strtoint16_safe(num, fcinfo->context)); } = /* diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 98d4323755..7d1767ce0f 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -52,7 +52,7 @@ int8in(PG_FUNCTION_ARGS) { char *num =3D PG_GETARG_CSTRING(0); = - PG_RETURN_INT64(pg_strtoint64(num)); + PG_RETURN_INT64(pg_strtoint64_safe(num, fcinfo->context)); } = = diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt= /jsonpath_exec.c index 8d83b2edb3..930bd26584 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -64,6 +64,7 @@ #include "funcapi.h" #include "lib/stringinfo.h" #include "miscadmin.h" +#include "nodes/miscnodes.h" #include "regex/regex.h" #include "utils/builtins.h" #include "utils/date.h" @@ -1041,15 +1042,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cx= t, JsonPathItem *jsp, char *tmp =3D DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(jb->val.numeric))); double val; - bool have_error =3D false; + ErrorSaveContext escontext =3D {T_ErrorSaveContext}; = - val =3D float8in_internal_opt_error(tmp, - NULL, - "double precision", - tmp, - &have_error); + val =3D float8in_internal(tmp, + NULL, + "double precision", + tmp, + (Node *) &escontext); = - if (have_error || isinf(val) || isnan(val)) + if (escontext.error_occurred || isinf(val) || isnan(val)) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), errmsg("numeric argument of jsonpath item method .%s() is ou= t of range for type double precision", @@ -1062,15 +1063,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cx= t, JsonPathItem *jsp, double val; char *tmp =3D pnstrdup(jb->val.string.val, jb->val.string.len); - bool have_error =3D false; + ErrorSaveContext escontext =3D {T_ErrorSaveContext}; = - val =3D float8in_internal_opt_error(tmp, - NULL, - "double precision", - tmp, - &have_error); + val =3D float8in_internal(tmp, + NULL, + "double precision", + tmp, + (Node *) &escontext); = - if (have_error || isinf(val) || isnan(val)) + if (escontext.error_occurred || isinf(val) || isnan(val)) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), errmsg("string argument of jsonpath item method .%s() is not= a valid representation of a double precision number", diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numer= ic.c index 7f0e93aa80..c024928bc8 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -497,8 +497,9 @@ static void alloc_var(NumericVar *var, int ndigits); static void free_var(NumericVar *var); static void zero_var(NumericVar *var); = -static const char *set_var_from_str(const char *str, const char *cp, - NumericVar *dest); +static bool set_var_from_str(const char *str, const char *cp, + NumericVar *dest, const char **endptr, + Node *escontext); static void set_var_from_num(Numeric num, NumericVar *dest); static void init_var_from_num(Numeric num, NumericVar *dest); static void set_var_from_var(const NumericVar *value, NumericVar *dest); @@ -512,8 +513,8 @@ static Numeric duplicate_numeric(Numeric num); static Numeric make_result(const NumericVar *var); static Numeric make_result_opt_error(const NumericVar *var, bool *have_er= ror); = -static void apply_typmod(NumericVar *var, int32 typmod); -static void apply_typmod_special(Numeric num, int32 typmod); +static bool apply_typmod(NumericVar *var, int32 typmod, Node *escontext); +static bool apply_typmod_special(Numeric num, int32 typmod, Node *esconte= xt); = static bool numericvar_to_int32(const NumericVar *var, int32 *result); static bool numericvar_to_int64(const NumericVar *var, int64 *result); @@ -617,11 +618,11 @@ Datum numeric_in(PG_FUNCTION_ARGS) { char *str =3D PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem =3D PG_GETARG_OID(1); #endif int32 typmod =3D PG_GETARG_INT32(2); + Node *escontext =3D fcinfo->context; Numeric res; const char *cp; = @@ -679,10 +680,12 @@ numeric_in(PG_FUNCTION_ARGS) * Use set_var_from_str() to parse a normal numeric value */ NumericVar value; + bool have_error; = init_var(&value); = - cp =3D set_var_from_str(str, cp, &value); + if (!set_var_from_str(str, cp, &value, &cp, escontext)) + PG_RETURN_NULL(); = /* * We duplicate a few lines of code here because we would like to @@ -693,16 +696,23 @@ numeric_in(PG_FUNCTION_ARGS) while (*cp) { if (!isspace((unsigned char) *cp)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "numeric", str))); cp++; } = - apply_typmod(&value, typmod); + if (!apply_typmod(&value, typmod, escontext)) + PG_RETURN_NULL(); + + res =3D make_result_opt_error(&value, &have_error); + + if (have_error) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); = - res =3D make_result(&value); free_var(&value); = PG_RETURN_NUMERIC(res); @@ -712,7 +722,7 @@ numeric_in(PG_FUNCTION_ARGS) while (*cp) { if (!isspace((unsigned char) *cp)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "numeric", str))); @@ -720,7 +730,8 @@ numeric_in(PG_FUNCTION_ARGS) } = /* As above, throw any typmod error after finishing syntax check */ - apply_typmod_special(res, typmod); + if (!apply_typmod_special(res, typmod, escontext)) + PG_RETURN_NULL(); = PG_RETURN_NUMERIC(res); } @@ -1058,7 +1069,7 @@ numeric_recv(PG_FUNCTION_ARGS) { trunc_var(&value, value.dscale); = - apply_typmod(&value, typmod); + (void) apply_typmod(&value, typmod, NULL); = res =3D make_result(&value); } @@ -1067,7 +1078,7 @@ numeric_recv(PG_FUNCTION_ARGS) /* apply_typmod_special wants us to make the Numeric first */ res =3D make_result(&value); = - apply_typmod_special(res, typmod); + (void) apply_typmod_special(res, typmod, NULL); } = free_var(&value); @@ -1180,7 +1191,7 @@ numeric (PG_FUNCTION_ARGS) */ if (NUMERIC_IS_SPECIAL(num)) { - apply_typmod_special(num, typmod); + (void) apply_typmod_special(num, typmod, NULL); PG_RETURN_NUMERIC(duplicate_numeric(num)); } = @@ -1231,7 +1242,7 @@ numeric (PG_FUNCTION_ARGS) init_var(&var); = set_var_from_num(num, &var); - apply_typmod(&var, typmod); + (void) apply_typmod(&var, typmod, NULL); new =3D make_result(&var); = free_var(&var); @@ -4395,6 +4406,7 @@ float8_numeric(PG_FUNCTION_ARGS) Numeric res; NumericVar result; char buf[DBL_DIG + 100]; + const char *endptr; = if (isnan(val)) PG_RETURN_NUMERIC(make_result(&const_nan)); @@ -4412,7 +4424,7 @@ float8_numeric(PG_FUNCTION_ARGS) init_var(&result); = /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result); + (void) set_var_from_str(buf, buf, &result, &endptr, NULL); = res =3D make_result(&result); = @@ -4488,6 +4500,7 @@ float4_numeric(PG_FUNCTION_ARGS) Numeric res; NumericVar result; char buf[FLT_DIG + 100]; + const char *endptr; = if (isnan(val)) PG_RETURN_NUMERIC(make_result(&const_nan)); @@ -4505,7 +4518,7 @@ float4_numeric(PG_FUNCTION_ARGS) init_var(&result); = /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result); + (void) set_var_from_str(buf, buf, &result, &endptr, NULL); = res =3D make_result(&result); = @@ -6804,14 +6817,19 @@ zero_var(NumericVar *var) * Parse a string and put the number into a variable * * This function does not handle leading or trailing spaces. It returns - * the end+1 position parsed, so that caller can check for trailing - * spaces/garbage if deemed necessary. + * the end+1 position parsed into *endptr, so that caller can check for + * trailing spaces/garbage if deemed necessary. * * cp is the place to actually start parsing; str is what to use in error * reports. (Typically cp would be the same except advanced over spaces.= ) + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static const char * -set_var_from_str(const char *str, const char *cp, NumericVar *dest) +static bool +set_var_from_str(const char *str, const char *cp, + NumericVar *dest, const char **endptr, + Node *escontext) { bool have_dp =3D false; int i; @@ -6849,7 +6867,7 @@ set_var_from_str(const char *str, const char *cp, Nu= mericVar *dest) } = if (!isdigit((unsigned char) *cp)) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "numeric", str))); @@ -6873,7 +6891,7 @@ set_var_from_str(const char *str, const char *cp, Nu= mericVar *dest) else if (*cp =3D=3D '.') { if (have_dp) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "numeric", str))); @@ -6897,7 +6915,7 @@ set_var_from_str(const char *str, const char *cp, Nu= mericVar *dest) cp++; exponent =3D strtol(cp, &endptr, 10); if (endptr =3D=3D cp) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "numeric", str))); @@ -6912,7 +6930,7 @@ set_var_from_str(const char *str, const char *cp, Nu= mericVar *dest) * for consistency use the same ereport errcode/text as make_result(). */ if (exponent >=3D INT_MAX / 2 || exponent <=3D -(INT_MAX / 2)) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value overflows numeric format"))); dweight +=3D (int) exponent; @@ -6963,7 +6981,9 @@ set_var_from_str(const char *str, const char *cp, Nu= mericVar *dest) strip_var(dest); = /* Return end+1 position for caller */ - return cp; + *endptr =3D cp; + + return true; } = = @@ -7455,9 +7475,12 @@ make_result(const NumericVar *var) * * Do bounds checking and rounding according to the specified typmod. * Note that this is only applied to normal finite values. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static void -apply_typmod(NumericVar *var, int32 typmod) +static bool +apply_typmod(NumericVar *var, int32 typmod, Node *escontext) { int precision; int scale; @@ -7467,7 +7490,7 @@ apply_typmod(NumericVar *var, int32 typmod) = /* Do nothing if we have an invalid typmod */ if (!is_valid_numeric_typmod(typmod)) - return; + return true; = precision =3D numeric_typmod_precision(typmod); scale =3D numeric_typmod_scale(typmod); @@ -7514,7 +7537,7 @@ apply_typmod(NumericVar *var, int32 typmod) #error unsupported NBASE #endif if (ddigits > maxdigits) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("numeric field overflow"), errdetail("A field with precision %d, scale %d must round to an a= bsolute value less than %s%d.", @@ -7528,6 +7551,8 @@ apply_typmod(NumericVar *var, int32 typmod) ddigits -=3D DEC_DIGITS; } } + + return true; } = /* @@ -7535,9 +7560,12 @@ apply_typmod(NumericVar *var, int32 typmod) * * Do bounds checking according to the specified typmod, for an Inf or Na= N. * For convenience of most callers, the value is presented in packed form= . + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static void -apply_typmod_special(Numeric num, int32 typmod) +static bool +apply_typmod_special(Numeric num, int32 typmod, Node *escontext) { int precision; int scale; @@ -7551,16 +7579,16 @@ apply_typmod_special(Numeric num, int32 typmod) * any finite number of digits. */ if (NUMERIC_IS_NAN(num)) - return; + return true; = /* Do nothing if we have a default typmod (-1) */ if (!is_valid_numeric_typmod(typmod)) - return; + return true; = precision =3D numeric_typmod_precision(typmod); scale =3D numeric_typmod_scale(typmod); = - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("numeric field overflow"), errdetail("A field with precision %d, scale %d cannot hold an infinit= e value.", diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numu= tils.c index 0de0bed0e8..ab1564f22d 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -88,15 +88,24 @@ decimalLength64(const uint64 v) /* * Convert input string to a signed 16 bit integer. * - * Allows any number of leading or trailing whitespace characters. Will t= hrow - * ereport() upon bad input format or overflow. + * Allows any number of leading or trailing whitespace characters. * + * pg_strtoint16() will throw ereport() upon bad input format or overflow= ; + * while pg_strtoint16_safe() instead returns such complaints in *esconte= xt, + * if it's an ErrorSaveContext. +* * NB: Accumulate input as an unsigned number, to deal with two's complem= ent * representation of the most negative number, which can't be represented= as a * signed positive number. */ int16 pg_strtoint16(const char *s) +{ + return pg_strtoint16_safe(s, NULL); +} + +int16 +pg_strtoint16_safe(const char *s, Node *escontext) { const char *ptr =3D s; uint16 tmp =3D 0; @@ -149,18 +158,16 @@ pg_strtoint16(const char *s) return (int16) tmp; = out_of_range: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value \"%s\" is out of range for type %s", s, "smallint"))); = invalid_syntax: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "smallint", s))); - - return 0; /* keep compiler quiet */ } = /* @@ -251,8 +258,11 @@ invalid_syntax: /* * Convert input string to a signed 64 bit integer. * - * Allows any number of leading or trailing whitespace characters. Will t= hrow - * ereport() upon bad input format or overflow. + * Allows any number of leading or trailing whitespace characters. + * + * pg_strtoint64() will throw ereport() upon bad input format or overflow= ; + * while pg_strtoint64_safe() instead returns such complaints in *esconte= xt, + * if it's an ErrorSaveContext. * * NB: Accumulate input as an unsigned number, to deal with two's complem= ent * representation of the most negative number, which can't be represented= as a @@ -260,6 +270,12 @@ invalid_syntax: */ int64 pg_strtoint64(const char *s) +{ + return pg_strtoint64_safe(s, NULL); +} + +int64 +pg_strtoint64_safe(const char *s, Node *escontext) { const char *ptr =3D s; uint64 tmp =3D 0; @@ -312,18 +328,16 @@ pg_strtoint64(const char *s) return (int64) tmp; = out_of_range: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value \"%s\" is out of range for type %s", s, "bigint"))); = invalid_syntax: - ereport(ERROR, + ereturn(escontext, 0, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "bigint", s))); - - return 0; /* keep compiler quiet */ } = /* diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index fbfd8375e3..10d13b0f1e 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -44,9 +44,11 @@ extern int namestrcmp(Name name, const char *str); = /* numutils.c */ extern int16 pg_strtoint16(const char *s); +extern int16 pg_strtoint16_safe(const char *s, Node *escontext); extern int32 pg_strtoint32(const char *s); extern int32 pg_strtoint32_safe(const char *s, Node *escontext); extern int64 pg_strtoint64(const char *s); +extern int64 pg_strtoint64_safe(const char *s, Node *escontext); extern int pg_itoa(int16 i, char *a); extern int pg_ultoa_n(uint32 value, char *a); extern int pg_ulltoa_n(uint64 value, char *a); diff --git a/src/include/utils/float.h b/src/include/utils/float.h index 4bf0e3ac07..f92860b4a4 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -42,10 +42,8 @@ extern void float_underflow_error(void) pg_attribute_no= return(); extern void float_zero_divide_error(void) pg_attribute_noreturn(); extern int is_infinite(float8 val); extern float8 float8in_internal(char *num, char **endptr_p, - const char *type_name, const char *orig_string); -extern float8 float8in_internal_opt_error(char *num, char **endptr_p, - const char *type_name, const char *orig_string, - bool *have_error); + const char *type_name, const char *orig_string, + struct Node *escontext); extern char *float8out_internal(float8 num); extern int float4_cmp_internal(float4 a, float4 b); extern int float8_cmp_internal(float8 a, float8 b); diff --git a/src/test/regress/expected/float4-misrounded-input.out b/src/t= est/regress/expected/float4-misrounded-input.out index 3d5d298b73..24fde6cc9f 100644 --- a/src/test/regress/expected/float4-misrounded-input.out +++ b/src/test/regress/expected/float4-misrounded-input.out @@ -81,6 +81,31 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); ERROR: invalid input syntax for type real: "123 5" LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'float4'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('xyz', 'float4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('1e400', 'float4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1e400', 'float4'); + pg_input_error_message = +--------------------------------------- + "1e400" is out of range for type real +(1 row) + -- special inputs SELECT 'NaN'::float4; float4 = diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expec= ted/float4.out index 6ad5d00aa2..1d7090a90d 100644 --- a/src/test/regress/expected/float4.out +++ b/src/test/regress/expected/float4.out @@ -81,6 +81,31 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); ERROR: invalid input syntax for type real: "123 5" LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'float4'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('xyz', 'float4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('1e400', 'float4'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1e400', 'float4'); + pg_input_error_message = +--------------------------------------- + "1e400" is out of range for type real +(1 row) + -- special inputs SELECT 'NaN'::float4; float4 = diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expec= ted/float8.out index de4d57ec9f..2b25784f7f 100644 --- a/src/test/regress/expected/float8.out +++ b/src/test/regress/expected/float8.out @@ -68,6 +68,31 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5'); ERROR: invalid input syntax for type double precision: "123 5" LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5'); ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'float8'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('xyz', 'float8'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('1e4000', 'float8'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1e4000', 'float8'); + pg_input_error_message = +---------------------------------------------------- + "1e4000" is out of range for type double precision +(1 row) + -- special inputs SELECT 'NaN'::float8; float8 = diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expecte= d/int2.out index 109cf9baaa..6a23567b67 100644 --- a/src/test/regress/expected/int2.out +++ b/src/test/regress/expected/int2.out @@ -45,6 +45,31 @@ SELECT * FROM INT2_TBL; -32767 (5 rows) = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int2'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('asdf', 'int2'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('50000', 'int2'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('50000', 'int2'); + pg_input_error_message = +------------------------------------------------- + value "50000" is out of range for type smallint +(1 row) + SELECT * FROM INT2_TBL AS f(a, b); ERROR: table "f" has 1 columns available but 2 columns specified SELECT * FROM (TABLE int2_tbl) AS s (a, b); diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expecte= d/int8.out index 1ae23cf3f9..90ed061249 100644 --- a/src/test/regress/expected/int8.out +++ b/src/test/regress/expected/int8.out @@ -42,6 +42,31 @@ SELECT * FROM INT8_TBL; 4567890123456789 | -4567890123456789 (5 rows) = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int8'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('asdf', 'int8'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('10000000000000000000', 'int8'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('10000000000000000000', 'int8'); + pg_input_error_message = +-------------------------------------------------------------- + value "10000000000000000000" is out of range for type bigint +(1 row) + -- int8/int8 cmp SELECT * FROM INT8_TBL WHERE q2 =3D 4567890123456789; q1 | q2 = diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expe= cted/numeric.out index 3c610646dc..30a5613ed7 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -2199,6 +2199,49 @@ SELECT * FROM num_input_test; -Infinity (13 rows) = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'numeric'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('34xyz', 'numeric'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('1e400000', 'numeric'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1e400000', 'numeric'); + pg_input_error_message = +-------------------------------- + value overflows numeric format +(1 row) + +SELECT pg_input_is_valid('1234.567', 'numeric(8,4)'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('1234.567', 'numeric(7,4)'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('1234.567', 'numeric(7,4)'); + pg_input_error_message = +------------------------ + numeric field overflow +(1 row) + -- -- Test precision and scale typemods -- diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4= .sql index 612486ecbd..061477726b 100644 --- a/src/test/regress/sql/float4.sql +++ b/src/test/regress/sql/float4.sql @@ -36,6 +36,12 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'float4'); +SELECT pg_input_is_valid('xyz', 'float4'); +SELECT pg_input_is_valid('1e400', 'float4'); +SELECT pg_input_error_message('1e400', 'float4'); + -- special inputs SELECT 'NaN'::float4; SELECT 'nan'::float4; diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8= .sql index 03c134b078..c276a5324c 100644 --- a/src/test/regress/sql/float8.sql +++ b/src/test/regress/sql/float8.sql @@ -34,6 +34,12 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('5. 0'); INSERT INTO FLOAT8_TBL(f1) VALUES (' - 3'); INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5'); = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'float8'); +SELECT pg_input_is_valid('xyz', 'float8'); +SELECT pg_input_is_valid('1e4000', 'float8'); +SELECT pg_input_error_message('1e4000', 'float8'); + -- special inputs SELECT 'NaN'::float8; SELECT 'nan'::float8; diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql index ea29066b78..98a761a24a 100644 --- a/src/test/regress/sql/int2.sql +++ b/src/test/regress/sql/int2.sql @@ -17,6 +17,12 @@ INSERT INTO INT2_TBL(f1) VALUES (''); = SELECT * FROM INT2_TBL; = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int2'); +SELECT pg_input_is_valid('asdf', 'int2'); +SELECT pg_input_is_valid('50000', 'int2'); +SELECT pg_input_error_message('50000', 'int2'); + SELECT * FROM INT2_TBL AS f(a, b); = SELECT * FROM (TABLE int2_tbl) AS s (a, b); diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql index 38b771964d..76007b692b 100644 --- a/src/test/regress/sql/int8.sql +++ b/src/test/regress/sql/int8.sql @@ -16,6 +16,12 @@ INSERT INTO INT8_TBL(q1) VALUES (''); = SELECT * FROM INT8_TBL; = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34', 'int8'); +SELECT pg_input_is_valid('asdf', 'int8'); +SELECT pg_input_is_valid('10000000000000000000', 'int8'); +SELECT pg_input_error_message('10000000000000000000', 'int8'); + -- int8/int8 cmp SELECT * FROM INT8_TBL WHERE q2 =3D 4567890123456789; SELECT * FROM INT8_TBL WHERE q2 <> 4567890123456789; diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numer= ic.sql index 93bb0996be..7bb34e5021 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -1053,6 +1053,15 @@ INSERT INTO num_input_test(n1) VALUES ('+ infinity'= ); = SELECT * FROM num_input_test; = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('34.5', 'numeric'); +SELECT pg_input_is_valid('34xyz', 'numeric'); +SELECT pg_input_is_valid('1e400000', 'numeric'); +SELECT pg_input_error_message('1e400000', 'numeric'); +SELECT pg_input_is_valid('1234.567', 'numeric(8,4)'); +SELECT pg_input_is_valid('1234.567', 'numeric(7,4)'); +SELECT pg_input_error_message('1234.567', 'numeric(7,4)'); + -- -- Test precision and scale typemods -- ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v5-0005-convert-cube.patch"; charset="us-ascii" Content-ID: <512546.1670540222.6@sss.pgh.pa.us> Content-Description: v5-0005-convert-cube.patch Content-Transfer-Encoding: quoted-printable diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c index 4f32c5dc1d..1fc447511a 100644 --- a/contrib/cube/cube.c +++ b/contrib/cube/cube.c @@ -123,8 +123,9 @@ cube_in(PG_FUNCTION_ARGS) = cube_scanner_init(str, &scanbuflen); = - cube_yyparse(&result, scanbuflen); + cube_yyparse(&result, scanbuflen, fcinfo->context); = + /* We might as well run this even on failure. */ cube_scanner_finish(); = PG_RETURN_NDBOX_P(result); diff --git a/contrib/cube/cubedata.h b/contrib/cube/cubedata.h index 640a7ca580..96fa41a04e 100644 --- a/contrib/cube/cubedata.h +++ b/contrib/cube/cubedata.h @@ -61,9 +61,12 @@ typedef struct NDBOX = /* in cubescan.l */ extern int cube_yylex(void); -extern void cube_yyerror(NDBOX **result, Size scanbuflen, const char *mes= sage) pg_attribute_noreturn(); +extern void cube_yyerror(NDBOX **result, Size scanbuflen, + struct Node *escontext, + const char *message); extern void cube_scanner_init(const char *str, Size *scanbuflen); extern void cube_scanner_finish(void); = /* in cubeparse.y */ -extern int cube_yyparse(NDBOX **result, Size scanbuflen); +extern int cube_yyparse(NDBOX **result, Size scanbuflen, + struct Node *escontext); diff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.y index e6e361736c..44450d1027 100644 --- a/contrib/cube/cubeparse.y +++ b/contrib/cube/cubeparse.y @@ -7,6 +7,7 @@ #include "postgres.h" = #include "cubedata.h" +#include "nodes/miscnodes.h" #include "utils/float.h" = /* All grammar constructs return strings */ @@ -21,14 +22,17 @@ #define YYFREE pfree = static int item_count(const char *s, char delim); -static NDBOX *write_box(int dim, char *str1, char *str2); -static NDBOX *write_point_as_box(int dim, char *str); +static bool write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext); +static bool write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext); = %} = /* BISON Declarations */ %parse-param {NDBOX **result} %parse-param {Size scanbuflen} +%parse-param {struct Node *escontext} %expect 0 %name-prefix=3D"cube_yy" = @@ -45,7 +49,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET dim =3D item_count($2, ','); if (item_count($4, ',') !=3D dim) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("Different point dimensions in (%s) and (%s).", @@ -54,7 +58,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET } if (dim > CUBE_MAX_DIM) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("A cube cannot have more than %d dimensions.", @@ -62,7 +66,8 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET YYABORT; } = - *result =3D write_box( dim, $2, $4 ); + if (!write_box(dim, $2, $4, result, escontext)) + YYABORT; } = | paren_list COMMA paren_list @@ -72,7 +77,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET dim =3D item_count($1, ','); if (item_count($3, ',') !=3D dim) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("Different point dimensions in (%s) and (%s).", @@ -81,7 +86,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET } if (dim > CUBE_MAX_DIM) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("A cube cannot have more than %d dimensions.", @@ -89,7 +94,8 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET YYABORT; } = - *result =3D write_box( dim, $1, $3 ); + if (!write_box(dim, $1, $3, result, escontext)) + YYABORT; } = | paren_list @@ -99,7 +105,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET dim =3D item_count($1, ','); if (dim > CUBE_MAX_DIM) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("A cube cannot have more than %d dimensions.", @@ -107,7 +113,8 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET YYABORT; } = - *result =3D write_point_as_box(dim, $1); + if (!write_point_as_box(dim, $1, result, escontext)) + YYABORT; } = | list @@ -117,7 +124,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET dim =3D item_count($1, ','); if (dim > CUBE_MAX_DIM) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), errdetail("A cube cannot have more than %d dimensions.", @@ -125,7 +132,8 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET YYABORT; } = - *result =3D write_point_as_box(dim, $1); + if (!write_point_as_box(dim, $1, result, escontext)) + YYABORT; } ; = @@ -173,8 +181,9 @@ item_count(const char *s, char delim) return nitems; } = -static NDBOX * -write_box(int dim, char *str1, char *str2) +static bool +write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext) { NDBOX *bp; char *s; @@ -190,18 +199,26 @@ write_box(int dim, char *str1, char *str2) s =3D str1; i =3D 0; if (dim > 0) - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, NULL); + { + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, NULL); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; } Assert(i =3D=3D dim); = s =3D str2; if (dim > 0) { - bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, NULL); + bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; /* code this way to do right thing with NaN */ point &=3D (bp->x[i] =3D=3D bp->x[0]); i++; @@ -209,7 +226,9 @@ write_box(int dim, char *str1, char *str2) while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, NULL); + bp->x[i] =3D float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; point &=3D (bp->x[i] =3D=3D bp->x[i - dim]); i++; } @@ -229,11 +248,13 @@ write_box(int dim, char *str1, char *str2) SET_POINT_BIT(bp); } = - return bp; + *result =3D bp; + return true; } = -static NDBOX * -write_point_as_box(int dim, char *str) +static bool +write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext) { NDBOX *bp; int i, @@ -250,13 +271,20 @@ write_point_as_box(int dim, char *str) s =3D str; i =3D 0; if (dim > 0) - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, NULL); + { + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } while ((s =3D strchr(s, ',')) !=3D NULL) { s++; - bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, NULL); + bp->x[i++] =3D float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; } Assert(i =3D=3D dim); = - return bp; + *result =3D bp; + return true; } diff --git a/contrib/cube/cubescan.l b/contrib/cube/cubescan.l index 6b316f2d54..49cb699216 100644 --- a/contrib/cube/cubescan.l +++ b/contrib/cube/cubescan.l @@ -72,11 +72,13 @@ NaN [nN][aA][nN] = /* result and scanbuflen are not used, but Bison expects this signature *= / void -cube_yyerror(NDBOX **result, Size scanbuflen, const char *message) +cube_yyerror(NDBOX **result, Size scanbuflen, + struct Node *escontext, + const char *message) { if (*yytext =3D=3D YY_END_OF_BUFFER_CHAR) { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), /* translator: %s is typically "syntax error" */ @@ -84,7 +86,7 @@ cube_yyerror(NDBOX **result, Size scanbuflen, const char= *message) } else { - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for cube"), /* translator: first %s is typically "syntax error" */ diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.o= ut index 5b89cb1a26..dc23e5ccc0 100644 --- a/contrib/cube/expected/cube.out +++ b/contrib/cube/expected/cube.out @@ -325,6 +325,31 @@ SELECT '-1e-700'::cube AS cube; -- out of range ERROR: "-1e-700" is out of range for type double precision LINE 1: SELECT '-1e-700'::cube AS cube; ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('(1,2)', 'cube'); + pg_input_is_valid = +------------------- + t +(1 row) + +SELECT pg_input_is_valid('[(1),]', 'cube'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_is_valid('-1e-700', 'cube'); + pg_input_is_valid = +------------------- + f +(1 row) + +SELECT pg_input_error_message('-1e-700', 'cube'); + pg_input_error_message = +----------------------------------------------------- + "-1e-700" is out of range for type double precision +(1 row) + -- -- Testing building cubes from float8 values -- diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql index 7f8b2e3979..384883d16e 100644 --- a/contrib/cube/sql/cube.sql +++ b/contrib/cube/sql/cube.sql @@ -79,6 +79,12 @@ SELECT '1,2a'::cube AS cube; -- 7 SELECT '1..2'::cube AS cube; -- 7 SELECT '-1e-700'::cube AS cube; -- out of range = +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('(1,2)', 'cube'); +SELECT pg_input_is_valid('[(1),]', 'cube'); +SELECT pg_input_is_valid('-1e-700', 'cube'); +SELECT pg_input_error_message('-1e-700', 'cube'); + -- -- Testing building cubes from float8 values -- ------- =_aaaaaaaaaa0--