Andres Freund <and...@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

diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index 693423e524..994dfc6526 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -900,6 +900,17 @@ CREATE TYPE <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/Makefile
index 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.pl
index 7212bc486f..08992dfd47 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -68,6 +68,7 @@ my @all_input_files = 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.c
index f5cd1b7493..eb489ea3a7 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -71,6 +71,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -611,6 +612,128 @@ errfinish(const char *filename, int lineno, const 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/README
index 49845f67ac..9958d38992 100644
--- a/src/backend/utils/fmgr/README
+++ b/src/backend/utils/fmgr/README
@@ -267,6 +267,78 @@ See windowapi.h for more information.
 information about the context of the CALL statement, particularly
 whether it is within an "atomic" execution context.
 
+* Some callers of datatype input functions (and in future perhaps
+other classes of functions) pass an instance of ErrorSaveContext.
+This indicates that the caller wishes to handle "soft" errors without
+a transaction-terminating exception being thrown: instead, the callee
+should store information about the error cause in the ErrorSaveContext
+struct and return a dummy result value.  Further details appear in
+"Handling Soft Errors" below.
+
+
+Handling Soft Errors
+--------------------
+
+Postgres' standard mechanism for reporting errors (ereport() or elog())
+is used for all sorts of error conditions.  This means that throwing
+an exception via ereport(ERROR) requires an expensive transaction or
+subtransaction abort and cleanup, since the exception catcher dare not
+make many assumptions about what has gone wrong.  There are situations
+where we would rather have a lighter-weight mechanism for dealing
+with errors that are known to be safe to recover from without a full
+transaction cleanup.  SQL-callable functions can support this need
+using the ErrorSaveContext context mechanism.
+
+To report a "soft" error, a SQL-callable function should call
+	errsave(fcinfo->context, ...)
+where it would previously have done
+	ereport(ERROR, ...)
+If the passed "context" is NULL or is not an ErrorSaveContext node,
+then errsave behaves precisely as ereport(ERROR): the exception is
+thrown via longjmp, so that control does not return.  If "context"
+is an ErrorSaveContext node, then the error information included in
+errsave's subsidiary reporting calls is stored into the context node
+and control returns from errsave normally.  The function should then
+return a dummy value to its caller.  (SQL NULL is recommendable as
+the dummy value; but anything will do, since the caller is expected
+to ignore the function's return value once it sees that an error has
+been reported in the ErrorSaveContext node.)
+
+If there is nothing to do except return after calling errsave(),
+you can save a line or two by writing
+	ereturn(fcinfo->context, dummy_value, ...)
+to perform errsave() and then "return dummy_value".
+
+An error reported "softly" must be safe, in the sense that there is
+no question about our ability to continue normal processing of the
+transaction.  Error conditions that should NOT be handled this way
+include out-of-memory, unexpected internal errors, or anything that
+cannot easily be cleaned up after.  Such cases should still be thrown
+with ereport, as they have been in the past.
+
+Considering datatype input functions as examples, typical "soft" error
+conditions include input syntax errors and out-of-range values.  An
+input function typically detects such cases with simple if-tests and
+can easily change the ensuing ereport call to an errsave or ereturn.
+Because of this restriction, it's typically not necessary to pass
+the ErrorSaveContext pointer down very far, as errors reported by
+low-level functions are typically reasonable to consider internal.
+(Another way to frame the distinction is that input functions should
+report all invalid-input conditions softly, but internal problems are
+hard errors.)
+
+Because no transaction cleanup will occur, a function that is exiting
+after errsave() returns will bear responsibility for resource cleanup.
+It is not necessary to be concerned about small leakages of palloc'd
+memory, since the caller should be running the function in a short-lived
+memory context.  However, resources such as locks, open files, or buffer
+pins must be closed out cleanly, as they would be in the non-error code
+path.
+
+Conventions for callers that use the ErrorSaveContext mechanism
+to trap errors are discussed with the declaration of that struct,
+in nodes/miscnodes.h.
+
 
 Functions Accepting or Returning Sets
 -------------------------------------
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 3c210297aa..a9519c6a8d 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -23,6 +23,7 @@
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "pgstat.h"
 #include "utils/acl.h"
@@ -1548,6 +1549,70 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
 	return result;
 }
 
+/*
+ * Call a previously-looked-up datatype input function, with non-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.h
index 380a82b9de..b7832d0aa2 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -700,6 +700,10 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid collation,
 /* Special cases for convenient invocation of datatype I/O functions. */
 extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str,
 							   Oid typioparam, int32 typmod);
+extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
+								  Oid typioparam, int32 typmod,
+								  fmNodePtr escontext,
+								  Datum *result);
 extern Datum OidInputFunctionCall(Oid functionId, char *str,
 								  Oid typioparam, int32 typmod);
 extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index e63881086e..f0e60935b6 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -16,6 +16,7 @@ node_support_input_i = [
   'nodes/bitmapset.h',
   'nodes/extensible.h',
   'nodes/lockoptions.h',
+  'nodes/miscnodes.h',
   'nodes/replnodes.h',
   'nodes/supportnodes.h',
   'nodes/value.h',
diff --git a/src/include/nodes/miscnodes.h b/src/include/nodes/miscnodes.h
new file mode 100644
index 0000000000..b50ee60352
--- /dev/null
+++ b/src/include/nodes/miscnodes.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * miscnodes.h
+ *	  Definitions for hard-to-classify node types.
+ *
+ * Node types declared here are not part of parse trees, plan trees,
+ * or execution state trees.  We only assign them NodeTag values because
+ * IsA() tests provide a convenient way to disambiguate what kind of
+ * structure is being passed through assorted APIs, such as function
+ * "context" pointers.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/nodes/miscnodes.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef MISCNODES_H
+#define MISCNODES_H
+
+#include "nodes/nodes.h"
+
+/*
+ * ErrorSaveContext -
+ *		function call context node for handling of "soft" errors
+ *
+ * A caller wishing to trap soft errors must initialize a struct like 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.h
index f107a818e8..8025dce335 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -18,6 +18,10 @@
 
 #include "lib/stringinfo.h"
 
+/* We cannot include nodes.h yet, so forward-declare struct Node */
+struct Node;
+
+
 /* Error level codes */
 #define DEBUG5		10			/* Debugging messages, in categories of
 								 * decreasing detail. */
@@ -235,6 +239,63 @@ extern int	getinternalerrposition(void);
 	ereport(elevel, errmsg_internal(__VA_ARGS__))
 
 
+/*----------
+ * Support for reporting "soft" errors that don't require a full 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);
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e57ffce971..ad31fdb737 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24683,6 +24683,107 @@ SELECT collation for ('foo' COLLATE "de_DE");
 
   </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.c
index 9c13251231..20a464fb59 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -32,6 +32,8 @@
 #include "common/keywords.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
+#include "parser/parse_type.h"
 #include "parser/scansup.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -45,6 +47,23 @@
 #include "utils/ruleutils.h"
 #include "utils/timestamp.h"
 
+
+/*
+ * structure to cache metadata needed in pg_input_is_valid_common
+ */
+typedef struct BasicIOData
+{
+	Oid			typoid;
+	Oid			typiofunc;
+	Oid			typioparam;
+	FmgrInfo	inputproc;
+} BasicIOData;
+
+static bool pg_input_is_valid_common(FunctionCallInfo fcinfo,
+									 text *txt, text *typname,
+									 ErrorSaveContext *escontext);
+
+
 /*
  * Common subroutine for num_nulls() and num_nonnulls().
  * Returns true if successful, false if function should return NULL.
@@ -640,6 +659,104 @@ pg_column_is_updatable(PG_FUNCTION_ARGS)
 }
 
 
+/*
+ * pg_input_is_valid - test whether string is valid input for datatype.
+ *
+ * Returns true if OK, false if not.
+ *
+ * This will only work usefully if the datatype's input function has been
+ * updated to return "soft" errors via errsave/ereturn.
+ */
+Datum
+pg_input_is_valid(PG_FUNCTION_ARGS)
+{
+	text	   *txt = 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.dat
index 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.out
index 0dfc88c1c8..7383fcdbb1 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -249,6 +249,31 @@ select format_type('bpchar'::regtype, -1);
  bpchar
 (1 row)
 
+-- Test non-error-throwing APIs using widget, which still throws errors
+SELECT pg_input_is_valid('(1,2,3)', 'widget');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('(1,2)', 'widget');  -- hard error expected
+ERROR:  invalid input syntax for type widget: "(1,2)"
+SELECT pg_input_is_valid('{"(1,2,3)"}', 'widget[]');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('{"(1,2)"}', 'widget[]');  -- hard error 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.c
index 548afb4438..2977045cc7 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -183,6 +183,11 @@ widget_in(PG_FUNCTION_ARGS)
 			coord[i++] = 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.sql
index c6fc4f9029..c25018029c 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -192,6 +192,14 @@ select format_type('bpchar'::regtype, null);
 -- this behavior difference is intentional
 select format_type('bpchar'::regtype, -1);
 
+-- Test non-error-throwing APIs using widget, which still throws errors
+SELECT pg_input_is_valid('(1,2,3)', 'widget');
+SELECT pg_input_is_valid('(1,2)', 'widget');  -- hard error expected
+SELECT pg_input_is_valid('{"(1,2,3)"}', 'widget[]');
+SELECT pg_input_is_valid('{"(1,2)"}', 'widget[]');  -- hard error 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)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 495e449a9e..c011ebdfd9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -90,14 +90,15 @@ typedef struct ArrayIteratorData
 }			ArrayIteratorData;
 
 static bool array_isspace(char ch);
-static int	ArrayCount(const char *str, int *dim, char typdelim);
-static void ReadArrayStr(char *arrayStr, const char *origStr,
+static int	ArrayCount(const char *str, int *dim, char typdelim,
+					   Node *escontext);
+static bool ReadArrayStr(char *arrayStr, const char *origStr,
 						 int nitems, int ndim, int *dim,
 						 FmgrInfo *inputproc, Oid typioparam, int32 typmod,
 						 char typdelim,
 						 int typlen, bool typbyval, char typalign,
 						 Datum *values, bool *nulls,
-						 bool *hasnulls, int32 *nbytes);
+						 bool *hasnulls, int32 *nbytes, Node *escontext);
 static void ReadArrayBinary(StringInfo buf, int nitems,
 							FmgrInfo *receiveproc, Oid typioparam, int32 typmod,
 							int typlen, bool typbyval, char typalign,
@@ -177,6 +178,7 @@ array_in(PG_FUNCTION_ARGS)
 	Oid			element_type = 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.c
index 051169a149..3821f6637b 100644
--- a/src/backend/utils/adt/arrayutils.c
+++ b/src/backend/utils/adt/arrayutils.c
@@ -74,6 +74,16 @@ ArrayGetOffset0(int n, const int *tup, const int *scale)
  */
 int
 ArrayGetNItems(int ndim, const int *dims)
+{
+	return ArrayGetNItemsSafe(ndim, dims, NULL);
+}
+
+/*
+ * This entry point can return the error into an ErrorSaveContext
+ * instead of throwing an exception.  -1 is returned after an error.
+ */
+int
+ArrayGetNItemsSafe(int ndim, const int *dims, struct Node *escontext)
 {
 	int32		ret;
 	int			i;
@@ -89,7 +99,7 @@ ArrayGetNItems(int ndim, const int *dims)
 
 		/* A negative dimension implies that UB-LB overflowed ... */
 		if (dims[i] < 0)
-			ereport(ERROR,
+			ereturn(escontext, -1,
 					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 					 errmsg("array size exceeds the maximum allowed (%d)",
 							(int) MaxArraySize)));
@@ -98,14 +108,14 @@ ArrayGetNItems(int ndim, const int *dims)
 
 		ret = (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.c
index cd7335287f..e291672ae4 100644
--- a/src/backend/utils/adt/bool.c
+++ b/src/backend/utils/adt/bool.c
@@ -148,13 +148,10 @@ boolin(PG_FUNCTION_ARGS)
 	if (parse_bool_with_len(str, len, &result))
 		PG_RETURN_BOOL(result);
 
-	ereport(ERROR,
+	ereturn(fcinfo->context, (Datum) 0,
 			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 			 errmsg("invalid input syntax for type %s: \"%s\"",
 					"boolean", in_str)));
-
-	/* not reached */
-	PG_RETURN_BOOL(false);
 }
 
 /*
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 42ddae99ef..e1837bee71 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -291,7 +291,7 @@ int4in(PG_FUNCTION_ARGS)
 {
 	char	   *num = 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.c
index a64422c8d0..0de0bed0e8 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -166,8 +166,11 @@ invalid_syntax:
 /*
  * Convert input string to a signed 32 bit integer.
  *
- * Allows any number of leading or trailing whitespace characters. Will 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.c
index db843a0fbf..bdafcff02d 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -77,6 +77,7 @@ record_in(PG_FUNCTION_ARGS)
 	char	   *string = 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.h
index 2f794d1168..3f6319aed5 100644
--- a/src/include/utils/array.h
+++ b/src/include/utils/array.h
@@ -447,7 +447,11 @@ extern void array_free_iterator(ArrayIterator 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.h
index 81631f1645..fbfd8375e3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -45,6 +45,7 @@ extern int	namestrcmp(Name name, const char *str);
 /* numutils.c */
 extern int16 pg_strtoint16(const char *s);
 extern int32 pg_strtoint32(const char *s);
+extern int32 pg_strtoint32_safe(const char *s, Node *escontext);
 extern int64 pg_strtoint64(const char *s);
 extern int	pg_itoa(int16 i, char *a);
 extern int	pg_ultoa_n(uint32 value, char *a);
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 97920f38c2..a2f9d7ed16 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -182,6 +182,31 @@ SELECT a,b,c FROM arrtest;
  [4:4]={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.out
index 4728fe2dfd..977124b20b 100644
--- a/src/test/regress/expected/boolean.out
+++ b/src/test/regress/expected/boolean.out
@@ -142,6 +142,25 @@ SELECT bool '' AS error;
 ERROR:  invalid input syntax for type boolean: ""
 LINE 1: SELECT bool '' AS error;
                     ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('true', 'bool');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('asdf', 'bool');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('junk', 'bool');
+            pg_input_error_message             
+-----------------------------------------------
+ invalid input syntax for type boolean: "junk"
+(1 row)
+
 -- and, or, not in qualifications
 SELECT bool 't' or bool 'f' AS true;
  true 
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index fbcc0e8d9e..b98007bd7a 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -45,6 +45,31 @@ SELECT * FROM INT4_TBL;
  -2147483647
 (5 rows)
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int4');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('asdf', 'int4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('1000000000000', 'int4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1000000000000', 'int4');
+                 pg_input_error_message                 
+--------------------------------------------------------
+ value "1000000000000" is out of range for type integer
+(1 row)
+
 SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
      f1      
 -------------
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index a4cc2d8c12..1bcd2b499c 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -69,6 +69,32 @@ ERROR:  malformed record literal: "(Joe,Blow) /"
 LINE 1: select '(Joe,Blow) /'::fullname;
                ^
 DETAIL:  Junk after right parenthesis.
+-- test non-error-throwing API
+create type twoints as (r integer, i integer);
+SELECT pg_input_is_valid('(1,2)', 'twoints');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('(1,2', 'twoints');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('(1,zed)', 'twoints');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('(1,zed)', 'twoints');
+            pg_input_error_message            
+----------------------------------------------
+ invalid input syntax for type integer: "zed"
+(1 row)
+
 create temp table quadtable(f1 int, q quad);
 insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
 insert into quadtable values (2, ((null,4.4),(5.5,6.6)));
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 791af5c0ce..38e8dd440b 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -113,6 +113,12 @@ SELECT a FROM arrtest WHERE a[2] IS NULL;
 DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL;
 SELECT a,b,c FROM arrtest;
 
+-- test non-error-throwing API
+SELECT pg_input_is_valid('{1,2,3}', 'integer[]');
+SELECT pg_input_is_valid('{1,2', 'integer[]');
+SELECT pg_input_is_valid('{1,zed}', 'integer[]');
+SELECT pg_input_error_message('{1,zed}', 'integer[]');
+
 -- test mixed slice/scalar subscripting
 select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
 select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
diff --git a/src/test/regress/sql/boolean.sql b/src/test/regress/sql/boolean.sql
index 4dd47aaf9d..dfaa55dd0f 100644
--- a/src/test/regress/sql/boolean.sql
+++ b/src/test/regress/sql/boolean.sql
@@ -62,6 +62,11 @@ SELECT bool '000' AS error;
 
 SELECT bool '' AS error;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('true', 'bool');
+SELECT pg_input_is_valid('asdf', 'bool');
+SELECT pg_input_error_message('junk', 'bool');
+
 -- and, or, not in qualifications
 
 SELECT bool 't' or bool 'f' AS true;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f19077f3da..54420818de 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -17,6 +17,12 @@ INSERT INTO INT4_TBL(f1) VALUES ('');
 
 SELECT * FROM INT4_TBL;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int4');
+SELECT pg_input_is_valid('asdf', 'int4');
+SELECT pg_input_is_valid('1000000000000', 'int4');
+SELECT pg_input_error_message('1000000000000', 'int4');
+
 SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
 
 SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int4 '0';
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index ad5b7e128f..4cd6a49215 100644
--- a/src/test/regress/sql/rowtypes.sql
+++ b/src/test/regress/sql/rowtypes.sql
@@ -31,6 +31,13 @@ select '[]'::fullname;          -- bad
 select ' (Joe,Blow)  '::fullname;  -- ok, extra whitespace
 select '(Joe,Blow) /'::fullname;  -- bad
 
+-- test non-error-throwing API
+create type twoints as (r integer, i integer);
+SELECT pg_input_is_valid('(1,2)', 'twoints');
+SELECT pg_input_is_valid('(1,2', 'twoints');
+SELECT pg_input_is_valid('(1,zed)', 'twoints');
+SELECT pg_input_error_message('(1,zed)', 'twoints');
+
 create temp table quadtable(f1 int, q quad);
 
 insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
diff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.y
index 977dcba965..e6e361736c 100644
--- a/contrib/cube/cubeparse.y
+++ b/contrib/cube/cubeparse.y
@@ -190,18 +190,18 @@ write_box(int dim, char *str1, char *str2)
 	s = 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.c
index da97538ebe..b02a19be24 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -163,6 +163,7 @@ Datum
 float4in(PG_FUNCTION_ARGS)
 {
 	char	   *num = 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.c
index d78002b901..721ce6634f 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -189,7 +189,7 @@ static float8
 single_decode(char *num, char **endptr_p,
 			  const char *type_name, const char *orig_string)
 {
-	return float8in_internal(num, endptr_p, type_name, orig_string);
+	return float8in_internal(num, endptr_p, type_name, orig_string, NULL);
 }								/* single_decode() */
 
 static void
@@ -212,7 +212,7 @@ pair_decode(char *str, float8 *x, float8 *y, char **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.c
index e1837bee71..8de38abd11 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -64,7 +64,7 @@ int2in(PG_FUNCTION_ARGS)
 {
 	char	   *num = PG_GETARG_CSTRING(0);
 
-	PG_RETURN_INT16(pg_strtoint16(num));
+	PG_RETURN_INT16(pg_strtoint16_safe(num, fcinfo->context));
 }
 
 /*
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 98d4323755..7d1767ce0f 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -52,7 +52,7 @@ int8in(PG_FUNCTION_ARGS)
 {
 	char	   *num = PG_GETARG_CSTRING(0);
 
-	PG_RETURN_INT64(pg_strtoint64(num));
+	PG_RETURN_INT64(pg_strtoint64_safe(num, fcinfo->context));
 }
 
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 8d83b2edb3..930bd26584 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -64,6 +64,7 @@
 #include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -1041,15 +1042,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *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.c
index 7f0e93aa80..c024928bc8 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -497,8 +497,9 @@ static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
 static void zero_var(NumericVar *var);
 
-static const char *set_var_from_str(const char *str, const char *cp,
-									NumericVar *dest);
+static bool set_var_from_str(const char *str, const char *cp,
+							 NumericVar *dest, const char **endptr,
+							 Node *escontext);
 static void set_var_from_num(Numeric num, NumericVar *dest);
 static void init_var_from_num(Numeric num, NumericVar *dest);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
@@ -512,8 +513,8 @@ static Numeric duplicate_numeric(Numeric num);
 static Numeric make_result(const NumericVar *var);
 static Numeric make_result_opt_error(const NumericVar *var, bool *have_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.c
index 0de0bed0e8..ab1564f22d 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -88,15 +88,24 @@ decimalLength64(const uint64 v)
 /*
  * Convert input string to a signed 16 bit integer.
  *
- * Allows any number of leading or trailing whitespace characters. Will 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.h
index fbfd8375e3..10d13b0f1e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -44,9 +44,11 @@ extern int	namestrcmp(Name name, const char *str);
 
 /* numutils.c */
 extern int16 pg_strtoint16(const char *s);
+extern int16 pg_strtoint16_safe(const char *s, Node *escontext);
 extern int32 pg_strtoint32(const char *s);
 extern int32 pg_strtoint32_safe(const char *s, Node *escontext);
 extern int64 pg_strtoint64(const char *s);
+extern int64 pg_strtoint64_safe(const char *s, Node *escontext);
 extern int	pg_itoa(int16 i, char *a);
 extern int	pg_ultoa_n(uint32 value, char *a);
 extern int	pg_ulltoa_n(uint64 value, char *a);
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 4bf0e3ac07..f92860b4a4 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -42,10 +42,8 @@ extern void float_underflow_error(void) pg_attribute_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.out
index 3d5d298b73..24fde6cc9f 100644
--- a/src/test/regress/expected/float4-misrounded-input.out
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -81,6 +81,31 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('123            5');
 ERROR:  invalid input syntax for type real: "123            5"
 LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123            5');
                                            ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'float4');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('xyz', 'float4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('1e400', 'float4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1e400', 'float4');
+        pg_input_error_message         
+---------------------------------------
+ "1e400" is out of range for type real
+(1 row)
+
 -- special inputs
 SELECT 'NaN'::float4;
  float4 
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 6ad5d00aa2..1d7090a90d 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -81,6 +81,31 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('123            5');
 ERROR:  invalid input syntax for type real: "123            5"
 LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123            5');
                                            ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'float4');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('xyz', 'float4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('1e400', 'float4');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1e400', 'float4');
+        pg_input_error_message         
+---------------------------------------
+ "1e400" is out of range for type real
+(1 row)
+
 -- special inputs
 SELECT 'NaN'::float4;
  float4 
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index de4d57ec9f..2b25784f7f 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -68,6 +68,31 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('123           5');
 ERROR:  invalid input syntax for type double precision: "123           5"
 LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('123           5');
                                            ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'float8');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('xyz', 'float8');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('1e4000', 'float8');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1e4000', 'float8');
+               pg_input_error_message               
+----------------------------------------------------
+ "1e4000" is out of range for type double precision
+(1 row)
+
 -- special inputs
 SELECT 'NaN'::float8;
  float8 
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 109cf9baaa..6a23567b67 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -45,6 +45,31 @@ SELECT * FROM INT2_TBL;
  -32767
 (5 rows)
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int2');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('asdf', 'int2');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('50000', 'int2');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('50000', 'int2');
+             pg_input_error_message              
+-------------------------------------------------
+ value "50000" is out of range for type smallint
+(1 row)
+
 SELECT * FROM INT2_TBL AS f(a, b);
 ERROR:  table "f" has 1 columns available but 2 columns specified
 SELECT * FROM (TABLE int2_tbl) AS s (a, b);
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 1ae23cf3f9..90ed061249 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -42,6 +42,31 @@ SELECT * FROM INT8_TBL;
  4567890123456789 | -4567890123456789
 (5 rows)
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int8');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('asdf', 'int8');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('10000000000000000000', 'int8');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('10000000000000000000', 'int8');
+                    pg_input_error_message                    
+--------------------------------------------------------------
+ value "10000000000000000000" is out of range for type bigint
+(1 row)
+
 -- int8/int8 cmp
 SELECT * FROM INT8_TBL WHERE q2 = 4567890123456789;
         q1        |        q2        
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 3c610646dc..30a5613ed7 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2199,6 +2199,49 @@ SELECT * FROM num_input_test;
  -Infinity
 (13 rows)
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'numeric');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('34xyz', 'numeric');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('1e400000', 'numeric');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1e400000', 'numeric');
+     pg_input_error_message     
+--------------------------------
+ value overflows numeric format
+(1 row)
+
+SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
+ pg_input_error_message 
+------------------------
+ numeric field overflow
+(1 row)
+
 --
 -- Test precision and scale typemods
 --
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 612486ecbd..061477726b 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -36,6 +36,12 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('5.   0');
 INSERT INTO FLOAT4_TBL(f1) VALUES ('     - 3.0');
 INSERT INTO FLOAT4_TBL(f1) VALUES ('123            5');
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'float4');
+SELECT pg_input_is_valid('xyz', 'float4');
+SELECT pg_input_is_valid('1e400', 'float4');
+SELECT pg_input_error_message('1e400', 'float4');
+
 -- special inputs
 SELECT 'NaN'::float4;
 SELECT 'nan'::float4;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 03c134b078..c276a5324c 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -34,6 +34,12 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('5.   0');
 INSERT INTO FLOAT8_TBL(f1) VALUES ('    - 3');
 INSERT INTO FLOAT8_TBL(f1) VALUES ('123           5');
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'float8');
+SELECT pg_input_is_valid('xyz', 'float8');
+SELECT pg_input_is_valid('1e4000', 'float8');
+SELECT pg_input_error_message('1e4000', 'float8');
+
 -- special inputs
 SELECT 'NaN'::float8;
 SELECT 'nan'::float8;
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index ea29066b78..98a761a24a 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -17,6 +17,12 @@ INSERT INTO INT2_TBL(f1) VALUES ('');
 
 SELECT * FROM INT2_TBL;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int2');
+SELECT pg_input_is_valid('asdf', 'int2');
+SELECT pg_input_is_valid('50000', 'int2');
+SELECT pg_input_error_message('50000', 'int2');
+
 SELECT * FROM INT2_TBL AS f(a, b);
 
 SELECT * FROM (TABLE int2_tbl) AS s (a, b);
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index 38b771964d..76007b692b 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -16,6 +16,12 @@ INSERT INTO INT8_TBL(q1) VALUES ('');
 
 SELECT * FROM INT8_TBL;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34', 'int8');
+SELECT pg_input_is_valid('asdf', 'int8');
+SELECT pg_input_is_valid('10000000000000000000', 'int8');
+SELECT pg_input_error_message('10000000000000000000', 'int8');
+
 -- int8/int8 cmp
 SELECT * FROM INT8_TBL WHERE q2 = 4567890123456789;
 SELECT * FROM INT8_TBL WHERE q2 <> 4567890123456789;
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 93bb0996be..7bb34e5021 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1053,6 +1053,15 @@ INSERT INTO num_input_test(n1) VALUES ('+ infinity');
 
 SELECT * FROM num_input_test;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('34.5', 'numeric');
+SELECT pg_input_is_valid('34xyz', 'numeric');
+SELECT pg_input_is_valid('1e400000', 'numeric');
+SELECT pg_input_error_message('1e400000', 'numeric');
+SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
+SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
+SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
+
 --
 -- Test precision and scale typemods
 --
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 4f32c5dc1d..1fc447511a 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -123,8 +123,9 @@ cube_in(PG_FUNCTION_ARGS)
 
 	cube_scanner_init(str, &scanbuflen);
 
-	cube_yyparse(&result, scanbuflen);
+	cube_yyparse(&result, scanbuflen, fcinfo->context);
 
+	/* We might as well run this even on failure. */
 	cube_scanner_finish();
 
 	PG_RETURN_NDBOX_P(result);
diff --git a/contrib/cube/cubedata.h b/contrib/cube/cubedata.h
index 640a7ca580..96fa41a04e 100644
--- a/contrib/cube/cubedata.h
+++ b/contrib/cube/cubedata.h
@@ -61,9 +61,12 @@ typedef struct NDBOX
 
 /* in cubescan.l */
 extern int	cube_yylex(void);
-extern void cube_yyerror(NDBOX **result, Size scanbuflen, const char *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.y
index e6e361736c..44450d1027 100644
--- a/contrib/cube/cubeparse.y
+++ b/contrib/cube/cubeparse.y
@@ -7,6 +7,7 @@
 #include "postgres.h"
 
 #include "cubedata.h"
+#include "nodes/miscnodes.h"
 #include "utils/float.h"
 
 /* All grammar constructs return strings */
@@ -21,14 +22,17 @@
 #define YYFREE   pfree
 
 static int item_count(const char *s, char delim);
-static NDBOX *write_box(int dim, char *str1, char *str2);
-static NDBOX *write_point_as_box(int dim, char *str);
+static bool write_box(int dim, char *str1, char *str2,
+					  NDBOX **result, struct Node *escontext);
+static bool write_point_as_box(int dim, char *str,
+							   NDBOX **result, struct Node *escontext);
 
 %}
 
 /* BISON Declarations */
 %parse-param {NDBOX **result}
 %parse-param {Size scanbuflen}
+%parse-param {struct Node *escontext}
 %expect 0
 %name-prefix="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.l
index 6b316f2d54..49cb699216 100644
--- a/contrib/cube/cubescan.l
+++ b/contrib/cube/cubescan.l
@@ -72,11 +72,13 @@ NaN          [nN][aA][nN]
 
 /* result and scanbuflen are not used, but Bison expects this signature */
 void
-cube_yyerror(NDBOX **result, Size scanbuflen, const char *message)
+cube_yyerror(NDBOX **result, Size scanbuflen,
+			 struct Node *escontext,
+			 const char *message)
 {
 	if (*yytext == 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.out
index 5b89cb1a26..dc23e5ccc0 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -325,6 +325,31 @@ SELECT '-1e-700'::cube AS cube; -- out of range
 ERROR:  "-1e-700" is out of range for type double precision
 LINE 1: SELECT '-1e-700'::cube AS cube;
                ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('(1,2)', 'cube');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('[(1),]', 'cube');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_is_valid('-1e-700', 'cube');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('-1e-700', 'cube');
+               pg_input_error_message                
+-----------------------------------------------------
+ "-1e-700" is out of range for type double precision
+(1 row)
+
 --
 -- Testing building cubes from float8 values
 --
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index 7f8b2e3979..384883d16e 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -79,6 +79,12 @@ SELECT '1,2a'::cube AS cube; -- 7
 SELECT '1..2'::cube AS cube; -- 7
 SELECT '-1e-700'::cube AS cube; -- out of range
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('(1,2)', 'cube');
+SELECT pg_input_is_valid('[(1),]', 'cube');
+SELECT pg_input_is_valid('-1e-700', 'cube');
+SELECT pg_input_error_message('-1e-700', 'cube');
+
 --
 -- Testing building cubes from float8 values
 --

Reply via email to