From: Tom Lane <tgl@sss.pgh.pa.us>
To: Andres Freund <andres@anarazel.de>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Andrew Dunstan <andrew@dunslane.net>
Cc: David G. Johnston <david.g.johnston@gmail.com>
Cc: Corey Huinker <corey.huinker@gmail.com>
Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>
Cc: Joe Conway <mail@joeconway.com>
Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Subject: Re: Error-safe user functions
Date: Thu, 08 Dec 2022 17:57:09 -0500
Message-ID: <512551.1670540229@sss.pgh.pa.us> (raw)
In-Reply-To: <20221208215836.3naznqmal2m32hzc@awork3.anarazel.de>
References: <3889877.1670358069@sss.pgh.pa.us>
<a6e7ac7e-30ec-5c9d-5134-16072aefc0d7@dunslane.net>
<4009543.1670422833@sss.pgh.pa.us>
<6acddf68-7cb7-e9e7-a5a7-0abc87b3c55b@dunslane.net>
<CAKFQuwYdQqQru9tpMpY4JpCEJ8OeN8kwmBG6DY2QyzE+5znA6Q@mail.gmail.com>
<4111458.1670444176@sss.pgh.pa.us>
<3211.1670452341@sss.pgh.pa.us>
<20221207233518.sudtaxn6omtvpzvr@awork3.anarazel.de>
<186288.1670517119@sss.pgh.pa.us>
<CA+TgmoZOhQ8FfWH9_r58tLtb=AwBn=7ugO2PHh6moR8jhYviUA@mail.gmail.com>
<20221208215836.3naznqmal2m32hzc@awork3.anarazel.de>
Andres Freund <andres@anarazel.de> 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
Attachments:download all (5 files, .tar.gz)v5-0001-infrastructure.patch (text/x-diff, 19.2K) downloaddiff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgmlindex 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 <replaceable class="parameter">name</replaceable>
function is written in C.
</para>
+ <para>+ In <productname>PostgreSQL</productname> version 16 and later,+ it is desirable for base types' input functions to+ return <quote>soft</quote> errors using the+ new <function>errsave()</function>/<function>ereturn()</function>+ mechanism, rather than throwing <function>ereport()</function>+ exceptions as in previous versions.+ See <filename>src/backend/utils/fmgr/README</filename> for more+ information.+ </para>+
</refsect1>
<refsect1>
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefileindex 4368c30fdb..7c594be583 100644--- a/src/backend/nodes/Makefile+++ b/src/backend/nodes/Makefile@@ -56,6 +56,7 @@ node_headers = \
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.plindex 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 = 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 = 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.cindex 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 char *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 wants+ * 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 further+ * 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 == NULL || !IsA(context, ErrorSaveContext))+ return errstart(ERROR, domain);++ /* Report that a soft error was detected */+ escontext = (ErrorSaveContext *) context;+ escontext->error_occurred = 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 = get_error_stack_entry();+ edata->elevel = 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 = 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 to+ * exist, in this code path.+ */+ edata->assoc_context = 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 = (ErrorSaveContext *) context;+ ErrorData *edata = &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 >= 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 = ERROR;++ /*+ * We skip calling backtrace and context functions, which are more likely+ * 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 = 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/READMEindex 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.cindex 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-exception+ * 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 and+ * 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 reported+ * 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 == NULL && flinfo->fn_strict)+ {+ *result = (Datum) 0; /* just return null result */+ return true;+ }++ InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, escontext, NULL);++ fcinfo->args[0].value = CStringGetDatum(str);+ fcinfo->args[0].isnull = false;+ fcinfo->args[1].value = ObjectIdGetDatum(typioparam);+ fcinfo->args[1].isnull = false;+ fcinfo->args[2].value = Int32GetDatum(typmod);+ fcinfo->args[2].isnull = false;++ *result = 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 == 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.hindex 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.buildindex e63881086e..f0e60935b6 100644--- a/src/include/nodes/meson.build+++ b/src/include/nodes/meson.build@@ -16,6 +16,7 @@ node_support_input_i = [
'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.hnew file mode 100644index 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 this+ * with all fields zero/NULL except for the NodeTag. Optionally, set+ * details_wanted = true if more than the bare knowledge that a soft error+ * occurred is required. The struct is then passed to a SQL-callable function+ * via the FunctionCallInfo.context field; or below the level of SQL calls,+ * 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_wanted+ * 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 called+ * 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) != NULL && IsA(escontext, ErrorSaveContext) && \+ ((ErrorSaveContext *) (escontext))->error_occurred)++#endif /* MISCNODES_H */diff --git a/src/include/utils/elog.h b/src/include/utils/elog.hindex 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 transaction+ * 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_ = (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);
v5-0002-add-test-scaffolding.patch (text/x-diff, 11.3K) downloaddiff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgmlindex 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");
</sect2>
+ <sect2 id="functions-info-validity">+ <title>Data Validity Checking Functions</title>++ <para>+ The functions shown in <xref linkend="functions-info-validity-table"/>+ can be helpful for checking validity of proposed input data.+ </para>++ <table id="functions-info-validity-table">+ <title>Data Validity Checking Functions</title>+ <tgroup cols="1">+ <thead>+ <row>+ <entry role="func_table_entry"><para role="func_signature">+ Function+ </para>+ <para>+ Description+ </para>+ <para>+ Example(s)+ </para></entry>+ </row>+ </thead>++ <tbody>+ <row>+ <entry role="func_table_entry"><para role="func_signature">+ <indexterm>+ <primary>pg_input_is_valid</primary>+ </indexterm>+ <function>pg_input_is_valid</function> (+ <parameter>string</parameter> <type>text</type>,+ <parameter>type</parameter> <type>text</type>+ )+ <returnvalue>boolean</returnvalue>+ </para>+ <para>+ Tests whether the given <parameter>string</parameter> is valid+ input for the specified data type, returning true or false.+ </para>+ <para>+ This function will only work as desired if the data type's input+ function has been updated to report invalid input as+ a <quote>soft</quote> error. Otherwise, invalid input will abort+ the transaction, just as if the string had been cast to the type+ directly.+ </para>+ <para>+ <literal>pg_input_is_valid('42', 'integer')</literal>+ <returnvalue>t</returnvalue>+ </para>+ <para>+ <literal>pg_input_is_valid('42000000000', 'integer')</literal>+ <returnvalue>f</returnvalue>+ </para>+ <para>+ <literal>pg_input_is_valid('1234.567', 'numeric(7,4)')</literal>+ <returnvalue>f</returnvalue>+ </para></entry>+ </row>+ <row>+ <entry role="func_table_entry"><para role="func_signature">+ <indexterm>+ <primary>pg_input_error_message</primary>+ </indexterm>+ <function>pg_input_error_message</function> (+ <parameter>string</parameter> <type>text</type>,+ <parameter>type</parameter> <type>text</type>+ )+ <returnvalue>text</returnvalue>+ </para>+ <para>+ Tests whether the given <parameter>string</parameter> 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 <function>pg_input_is_valid</function>.+ </para>+ <para>+ This function will only work as desired if the data type's input+ function has been updated to report invalid input as+ a <quote>soft</quote> error. Otherwise, invalid input will abort+ the transaction, just as if the string had been cast to the type+ directly.+ </para>+ <para>+ <literal>pg_input_error_message('42000000000', 'integer')</literal>+ <returnvalue>value "42000000000" is out of range for type integer</returnvalue>+ </para>+ <para>+ <literal>pg_input_error_message('1234.567', 'numeric(7,4)')</literal>+ <returnvalue>numeric field overflow</returnvalue>+ </para></entry>+ </row>+ </tbody>+ </tgroup>+ </table>++ </sect2>+
<sect2 id="functions-info-snapshot">
<title>Transaction ID and Snapshot Information Functions</title>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.cindex 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 = PG_GETARG_TEXT_PP(0);+ text *typname = PG_GETARG_TEXT_PP(1);+ ErrorSaveContext escontext = {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 datatype.+ *+ * 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 = PG_GETARG_TEXT_PP(0);+ text *typname = PG_GETARG_TEXT_PP(1);+ ErrorSaveContext escontext = {T_ErrorSaveContext};++ /* Enable details_wanted */+ escontext.details_wanted = true;++ if (pg_input_is_valid_common(fcinfo, txt, typname,+ &escontext))+ PG_RETURN_NULL();++ Assert(escontext.error_occurred);+ Assert(escontext.error_data != NULL);+ Assert(escontext.error_data->message != 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 = text_to_cstring(txt);+ char *typnamestr = 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 = (BasicIOData *) fcinfo->flinfo->fn_extra;+ if (my_extra == NULL)+ {+ fcinfo->flinfo->fn_extra =+ MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,+ sizeof(BasicIOData));+ my_extra = (BasicIOData *) fcinfo->flinfo->fn_extra;+ my_extra->typoid = InvalidOid;+ }++ if (my_extra->typoid != 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 = 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.datindex f9301b2627..719599649a 100644--- a/src/include/catalog/pg_proc.dat+++ b/src/include/catalog/pg_proc.dat@@ -7060,6 +7060,14 @@
prorettype => 'regnamespace', proargtypes => 'text',
prosrc => 'to_regnamespace' },
+{ oid => '8050', descr => 'test whether string is valid input for data type',+ proname => 'pg_input_is_valid', provolatile => 's', prorettype => 'bool',+ proargtypes => 'text text', prosrc => 'pg_input_is_valid' },+{ oid => '8051',+ descr => 'get error message if string is not valid input for data type',+ proname => 'pg_input_error_message', provolatile => 's', prorettype => 'text',+ proargtypes => 'text text', prosrc => 'pg_input_error_message' },+
{ oid => '1268',
descr => 'parse qualified identifier to array of identifiers',
proname => 'parse_ident', prorettype => '_text', proargtypes => 'text bool',
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.outindex 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 expected+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.cindex 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++] = 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 that+ * 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/create_type.sqlindex 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 expected+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)
v5-0003-convert-a-few-data-types.patch (text/x-diff, 30.9K) downloaddiff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.cindex 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 = PG_GETARG_OID(1); /* type of an array
* element */
int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */
+ Node *escontext = 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 >= 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 = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++)
/* skip */ ;
if (q == 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 dimensions.")));
@@ -280,7 +282,7 @@ array_in(PG_FUNCTION_ARGS)
for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++)
/* skip */ ;
if (q == 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] = 1;
}
if (*q != ']')
- 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 = atoi(p);
p = 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 != '{')
- 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 information.")));
- ndim = ArrayCount(p, dim, typdelim);+ ndim = ArrayCount(p, dim, typdelim, escontext);+ if (ndim < 0)+ PG_RETURN_NULL();
for (i = 0; i < ndim; i++)
lBound[i] = 1;
}
@@ -328,7 +332,7 @@ array_in(PG_FUNCTION_ARGS)
/* If array dimensions are given, expect '=' operator */
if (strncmp(p, ASSGN, strlen(ASSGN)) != 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 != '{')
- 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 = ArrayCount(p, dim_braces, typdelim);+ ndim_braces = ArrayCount(p, dim_braces, typdelim, escontext);+ if (ndim_braces < 0)+ PG_RETURN_NULL();
if (ndim_braces != 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 = 0; i < ndim; ++i)
{
if (dim[i] != 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 = ArrayGetNItems(ndim, dim);- ArrayCheckBounds(ndim, dim, lBound);+ nitems = ArrayGetNItemsSafe(ndim, dim, escontext);+ if (nitems < 0)+ PG_RETURN_NULL();+ if (!ArrayCheckBoundsSafe(ndim, dim, lBound, escontext))+ PG_RETURN_NULL();
/* Empty array? */
if (nitems == 0)
@@ -381,13 +390,14 @@ array_in(PG_FUNCTION_ARGS)
dataPtr = (Datum *) palloc(nitems * sizeof(Datum));
nullsPtr = (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 = 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 = 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 != ARRAY_ELEM_STARTED &&
parse_state != ARRAY_QUOTED_ELEM_STARTED &&
parse_state != 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 != ARRAY_LEVEL_STARTED &&
parse_state != ARRAY_QUOTED_ELEM_STARTED &&
parse_state != 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 != ARRAY_NO_LEVEL &&
parse_state != ARRAY_LEVEL_STARTED &&
parse_state != 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 = ARRAY_LEVEL_STARTED;
if (nest_level >= MAXDIM)
- ereport(ERROR,+ ereturn(escontext, -1,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
nest_level + 1, MAXDIM)));
@@ -581,14 +593,14 @@ ArrayCount(const char *str, int *dim, char typdelim)
parse_state != ARRAY_QUOTED_ELEM_COMPLETED &&
parse_state != ARRAY_LEVEL_COMPLETED &&
!(nest_level == 1 && parse_state == 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 = ARRAY_LEVEL_COMPLETED;
if (nest_level == 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] != 0 &&
nelems[nest_level] != 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 != ARRAY_ELEM_COMPLETED &&
parse_state != ARRAY_QUOTED_ELEM_COMPLETED &&
parse_state != 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 != ARRAY_LEVEL_STARTED &&
parse_state != ARRAY_ELEM_STARTED &&
parse_state != 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 have
* 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 = 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 == '\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 >= 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 == 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 = '\0';
if (i < 0 || i >= 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") == 0)
{
/* it's a NULL item */
- values[i] = InputFunctionCall(inputproc, NULL,- typioparam, typmod);+ if (!InputFunctionCallSafe(inputproc, NULL,+ typioparam, typmod,+ escontext,+ &values[i]))+ return false;
nulls[i] = true;
}
else
{
- values[i] = InputFunctionCall(inputproc, itemstart,- typioparam, typmod);+ if (!InputFunctionCallSafe(inputproc, itemstart,+ typioparam, typmod,+ escontext,+ &values[i]))+ return false;
nulls[i] = false;
}
}
@@ -930,7 +954,7 @@ ReadArrayStr(char *arrayStr,
totbytes = 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 = hasnull;
*nbytes = totbytes;
+ return true;
}
diff --git a/src/backend/utils/adt/arrayutils.c b/src/backend/utils/adt/arrayutils.cindex 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 = (int32) prod;
if ((int64) ret != prod)
- ereport(ERROR,+ ereturn(escontext, -1,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("array size exceeds the maximum allowed (%d)",
(int) MaxArraySize)));
}
Assert(ret >= 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 int *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.cindex 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.cindex 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 = 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/numutils.cindex 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 throw- * 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 *escontext,+ * if it's an ErrorSaveContext.
*
* NB: Accumulate input as an unsigned number, to deal with two's complement
* 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 = s;
uint32 tmp = 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/rowtypes.cindex 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 = PG_GETARG_CSTRING(0);
Oid tupType = PG_GETARG_OID(1);
int32 tupTypmod = PG_GETARG_INT32(2);
+ Node *escontext = 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 == 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++ != '(')
- 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 = *ptr++;
if (ch == '\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 == '\\')
{
if (*ptr == '\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 == '"')
@@ -252,10 +265,13 @@ record_in(PG_FUNCTION_ARGS)
column_info->column_type = column_type;
}
- values[i] = 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++ != ')')
- 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 = 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.hindex 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 iterator);
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 *endp);
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, const int *span);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.hindex 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/expected/arrays.outindex 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]={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/expected/boolean.outindex 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/expected/int4.outindex 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/expected/rowtypes.outindex 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.sqlindex 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/boolean.sqlindex 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.sqlindex 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/rowtypes.sqlindex 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)));
v5-0004-convert-numeric-types.patch (text/x-diff, 35.9K) downloaddiff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.yindex 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 = str1;
i = 0;
if (dim > 0)
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str1);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, NULL);
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str1);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, NULL);
}
Assert(i == dim);
s = str2;
if (dim > 0)
{
- bp->x[i] = float8in_internal(s, &endptr, "cube", str2);+ bp->x[i] = float8in_internal(s, &endptr, "cube", str2, NULL);
/* code this way to do right thing with NaN */
point &= (bp->x[i] == bp->x[0]);
i++;
@@ -209,7 +209,7 @@ write_box(int dim, char *str1, char *str2)
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i] = float8in_internal(s, &endptr, "cube", str2);+ bp->x[i] = float8in_internal(s, &endptr, "cube", str2, NULL);
point &= (bp->x[i] == bp->x[i - dim]);
i++;
}
@@ -250,11 +250,11 @@ write_point_as_box(int dim, char *str)
s = str;
i = 0;
if (dim > 0)
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str, NULL);
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str, NULL);
}
Assert(i == dim);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.cindex 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 = PG_GETARG_CSTRING(0);
+ Node *escontext = fcinfo->context;
char *orig_num;
float val;
char *endptr;
@@ -183,7 +184,7 @@ float4in(PG_FUNCTION_ARGS)
* strtod() on different platforms.
*/
if (*num == '\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 >= HUGE_VALF || val <= -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 != '\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 = 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 = 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 instead+ * 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 don'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 = false;-
/* skip leading whitespace */
while (*num != '\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 == '\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 = 0;
val = strtod(num, &endptr);
@@ -469,20 +457,17 @@ float8in_internal_opt_error(char *num, char **endptr_p,
char *errnumber = pstrdup(num);
errnumber[endptr - num] = '\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 = endptr;
else if (*endptr != '\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" argument.- */-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_ops.cindex 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 **endptr_p,
if ((has_delim = (*str == LDELIM)))
str++;
- *x = float8in_internal(str, &str, type_name, orig_string);+ *x = float8in_internal(str, &str, type_name, orig_string, NULL);
if (*str++ != DELIM)
ereport(ERROR,
@@ -220,7 +220,7 @@ pair_decode(char *str, float8 *x, float8 *y, char **endptr_p,
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
- *y = float8in_internal(str, &str, type_name, orig_string);+ *y = 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.cindex 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 = 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.cindex 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 = 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.cindex 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 *cxt, JsonPathItem *jsp,
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric)));
double val;
- bool have_error = false;+ ErrorSaveContext escontext = {T_ErrorSaveContext};- val = float8in_internal_opt_error(tmp,- NULL,- "double precision",- tmp,- &have_error);+ val = 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 out of range for type double precision",
@@ -1062,15 +1063,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
double val;
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
- bool have_error = false;+ ErrorSaveContext escontext = {T_ErrorSaveContext};- val = float8in_internal_opt_error(tmp,- NULL,- "double precision",- tmp,- &have_error);+ val = 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/numeric.cindex 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_error);
-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 *escontext);
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 = PG_GETARG_CSTRING(0);
-
#ifdef NOT_USED
Oid typelem = PG_GETARG_OID(1);
#endif
int32 typmod = PG_GETARG_INT32(2);
+ Node *escontext = 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 = 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 = 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 = 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 = make_result(&value);
}
@@ -1067,7 +1078,7 @@ numeric_recv(PG_FUNCTION_ARGS)
/* apply_typmod_special wants us to make the Numeric first */
res = 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 = 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 = 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 = 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 = false;
int i;
@@ -6849,7 +6867,7 @@ set_var_from_str(const char *str, const char *cp, NumericVar *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, NumericVar *dest)
else if (*cp == '.')
{
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, NumericVar *dest)
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == 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, NumericVar *dest)
* for consistency use the same ereport errcode/text as make_result().
*/
if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
- ereport(ERROR,+ ereturn(escontext, false,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
dweight += (int) exponent;
@@ -6963,7 +6981,9 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
strip_var(dest);
/* Return end+1 position for caller */
- return cp;+ *endptr = 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 = numeric_typmod_precision(typmod);
scale = 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 absolute value less than %s%d.",
@@ -7528,6 +7551,8 @@ apply_typmod(NumericVar *var, int32 typmod)
ddigits -= 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 NaN.
* 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 = numeric_typmod_precision(typmod);
scale = 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 infinite value.",
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.cindex 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 throw- * 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 *escontext,+ * if it's an ErrorSaveContext.+*
* NB: Accumulate input as an unsigned number, to deal with two's complement
* 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 = s;
uint16 tmp = 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 throw- * 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 *escontext,+ * if it's an ErrorSaveContext.
*
* NB: Accumulate input as an unsigned number, to deal with two's complement
* 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 = s;
uint64 tmp = 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.hindex 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.hindex 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_noreturn();
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/test/regress/expected/float4-misrounded-input.outindex 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/expected/float4.outindex 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/expected/float8.outindex 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/expected/int2.outindex 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/expected/int8.outindex 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 = 4567890123456789;
q1 | q2
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.outindex 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.sqlindex 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.sqlindex 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.sqlindex 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.sqlindex 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 = 4567890123456789;
SELECT * FROM INT8_TBL WHERE q2 <> 4567890123456789;
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sqlindex 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
--
v5-0005-convert-cube.patch (text/x-diff, 9.8K) downloaddiff --git a/contrib/cube/cube.c b/contrib/cube/cube.cindex 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.hindex 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 *message) 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.yindex 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="cube_yy"
@@ -45,7 +49,7 @@ box: O_BRACKET paren_list COMMA paren_list C_BRACKET
dim = item_count($2, ',');
if (item_count($4, ',') != 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 = 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 = item_count($1, ',');
if (item_count($3, ',') != 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 = 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 = 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 = 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 = 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 = 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 = str1;
i = 0;
if (dim > 0)
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, NULL);+ {+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;+ }
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, NULL);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;
}
Assert(i == dim);
s = str2;
if (dim > 0)
{
- bp->x[i] = float8in_internal(s, &endptr, "cube", str2, NULL);+ bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;
/* code this way to do right thing with NaN */
point &= (bp->x[i] == bp->x[0]);
i++;
@@ -209,7 +226,9 @@ write_box(int dim, char *str1, char *str2)
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i] = float8in_internal(s, &endptr, "cube", str2, NULL);+ bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;
point &= (bp->x[i] == bp->x[i - dim]);
i++;
}
@@ -229,11 +248,13 @@ write_box(int dim, char *str1, char *str2)
SET_POINT_BIT(bp);
}
- return bp;+ *result = 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 = str;
i = 0;
if (dim > 0)
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str, NULL);+ {+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;+ }
while ((s = strchr(s, ',')) != NULL)
{
s++;
- bp->x[i++] = float8in_internal(s, &endptr, "cube", str, NULL);+ bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext);+ if (SOFT_ERROR_OCCURRED(escontext))+ return false;
}
Assert(i == dim);
- return bp;+ *result = bp;+ return true;
}
diff --git a/contrib/cube/cubescan.l b/contrib/cube/cubescan.lindex 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 == 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.outindex 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.sqlindex 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
--
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: tgl@sss.pgh.pa.us, andres@anarazel.de, robertmhaas@gmail.com, andrew@dunslane.net, david.g.johnston@gmail.com, corey.huinker@gmail.com, alvherre@alvh.no-ip.org, mail@joeconway.com, n.gluhov@postgrespro.ru, pgsql-hackers@lists.postgresql.org
Subject: Re: Error-safe user functions
In-Reply-To: <512551.1670540229@sss.pgh.pa.us>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox