static uint32 cached_db_hash;
 
 
-static const char *getid(const char *s, char *n);
+static const char *getid(const char *s, char *n, Node *escontext);
 static void putid(char *p, const char *s);
 static Acl *allocacl(int n);
 static void check_acl(const Acl *acl);
-static const char *aclparse(const char *s, AclItem *aip);
+static const char *aclparse(const char *s, AclItem *aip, Node *escontext);
 static bool aclitem_match(const AclItem *a1, const AclItem *a2);
 static int aclitemComparator(const void *arg1, const void *arg2);
 static void check_circularity(const Acl *old_acl, const AclItem *mod_aip,
  *     in 's', after any quotes.  Also:
  *     - loads the identifier into 'n'.  (If no identifier is found, 'n'
  *       contains an empty string.)  'n' must be NAMEDATALEN bytes.
+ *
+ * Errors are reported via ereport, unless escontext is an ErrorSaveData node,
+ * in which case we log the error there and return NULL.
  */
 static const char *
-getid(const char *s, char *n)
+getid(const char *s, char *n, Node *escontext)
 {
    int         len = 0;
    bool        in_quotes = false;
 
        /* Add the character to the string */
        if (len >= NAMEDATALEN - 1)
-           ereport(ERROR,
+           ereturn(escontext, NULL,
                    (errcode(ERRCODE_NAME_TOO_LONG),
                     errmsg("identifier too long"),
                     errdetail("Identifier must be less than %d characters.",
  *     specification.  Also:
  *     - loads the structure pointed to by 'aip' with the appropriate
  *       UID/GID, id type identifier and mode type values.
+ *
+ * Errors are reported via ereport, unless escontext is an ErrorSaveData node,
+ * in which case we log the error there and return NULL.
  */
 static const char *
-aclparse(const char *s, AclItem *aip)
+aclparse(const char *s, AclItem *aip, Node *escontext)
 {
    AclMode     privs,
                goption,
 
    Assert(s && aip);
 
-   s = getid(s, name);
+   s = getid(s, name, escontext);
+   if (s == NULL)
+       return NULL;
    if (*s != '=')
    {
        /* we just read a keyword, not a name */
        if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0)
-           ereport(ERROR,
+           ereturn(escontext, NULL,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("unrecognized key word: \"%s\"", name),
                     errhint("ACL key word must be \"group\" or \"user\".")));
-       s = getid(s, name);     /* move s to the name beyond the keyword */
+       /* move s to the name beyond the keyword */
+       s = getid(s, name, escontext);
+       if (s == NULL)
+           return NULL;
        if (name[0] == '\0')
-           ereport(ERROR,
+           ereturn(escontext, NULL,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("missing name"),
                     errhint("A name must follow the \"group\" or \"user\" key word.")));
    }
 
    if (*s != '=')
-       ereport(ERROR,
+       ereturn(escontext, NULL,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("missing \"=\" sign")));
 
                read = 0;
                break;
            default:
-               ereport(ERROR,
+               ereturn(escontext, NULL,
                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                         errmsg("invalid mode character: must be one of \"%s\"",
                                ACL_ALL_RIGHTS_STR)));
    if (name[0] == '\0')
        aip->ai_grantee = ACL_ID_PUBLIC;
    else
-       aip->ai_grantee = get_role_oid(name, false);
+   {
+       aip->ai_grantee = get_role_oid(name, true);
+       if (!OidIsValid(aip->ai_grantee))
+           ereturn(escontext, NULL,
+                   (errcode(ERRCODE_UNDEFINED_OBJECT),
+                    errmsg("role \"%s\" does not exist", name)));
+   }
 
    /*
     * XXX Allow a degree of backward compatibility by defaulting the grantor
     */
    if (*s == '/')
    {
-       s = getid(s + 1, name2);
+       s = getid(s + 1, name2, escontext);
+       if (s == NULL)
+           return NULL;
        if (name2[0] == '\0')
-           ereport(ERROR,
+           ereturn(escontext, NULL,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("a name must follow the \"/\" sign")));
-       aip->ai_grantor = get_role_oid(name2, false);
+       aip->ai_grantor = get_role_oid(name2, true);
+       if (!OidIsValid(aip->ai_grantor))
+           ereturn(escontext, NULL,
+                   (errcode(ERRCODE_UNDEFINED_OBJECT),
+                    errmsg("role \"%s\" does not exist", name2)));
    }
    else
    {
 aclitemin(PG_FUNCTION_ARGS)
 {
    const char *s = PG_GETARG_CSTRING(0);
+   Node       *escontext = fcinfo->context;
    AclItem    *aip;
 
    aip = (AclItem *) palloc(sizeof(AclItem));
-   s = aclparse(s, aip);
+
+   s = aclparse(s, aip, escontext);
+   if (s == NULL)
+       PG_RETURN_NULL();
+
    while (isspace((unsigned char) *s))
        ++s;
    if (*s)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("extra garbage at the end of the ACL specification")));
 
 
 int2vectorin(PG_FUNCTION_ARGS)
 {
    char       *intString = PG_GETARG_CSTRING(0);
+   Node       *escontext = fcinfo->context;
    int2vector *result;
    int         n;
 
        l = strtol(intString, &endp, 10);
 
        if (intString == endp)
-           ereport(ERROR,
+           ereturn(escontext, (Datum) 0,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("invalid input syntax for type %s: \"%s\"",
                            "smallint", intString)));
 
        if (errno == ERANGE || l < SHRT_MIN || l > SHRT_MAX)
-           ereport(ERROR,
+           ereturn(escontext, (Datum) 0,
                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                     errmsg("value \"%s\" is out of range for type %s", intString,
                            "smallint")));
 
        if (*endp && *endp != ' ')
-           ereport(ERROR,
+           ereturn(escontext, (Datum) 0,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("invalid input syntax for type %s: \"%s\"",
                            "integer", intString)));
    while (*intString && isspace((unsigned char) *intString))
        intString++;
    if (*intString)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("int2vector has too many elements")));
 
 
 
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
 #include "nodes/value.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
  *  USER I/O ROUTINES                                                       *
  *****************************************************************************/
 
+/*
+ * Parse a single OID and return its value.
+ *
+ * If endloc isn't NULL, store a pointer to the rest of the string there,
+ * so that caller can parse the rest.  Otherwise, it's an error if anything
+ * but whitespace follows.
+ *
+ * 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.
+ */
 static Oid
-oidin_subr(const char *s, char **endloc)
+oidin_subr(const char *s, char **endloc, Node *escontext)
 {
    unsigned long cvt;
    char       *endptr;
    Oid         result;
 
    if (*s == '\0')
-       ereport(ERROR,
+       ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "oid", s)));
     * handled by the second "if" consistent across platforms.
     */
    if (errno && errno != ERANGE && errno != EINVAL)
-       ereport(ERROR,
+       ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "oid", s)));
 
    if (endptr == s && *s != '\0')
-       ereport(ERROR,
+       ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "oid", s)));
 
    if (errno == ERANGE)
-       ereport(ERROR,
+       ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                 errmsg("value \"%s\" is out of range for type %s",
                        s, "oid")));
        while (*endptr && isspace((unsigned char) *endptr))
            endptr++;
        if (*endptr)
-           ereport(ERROR,
+           ereturn(escontext, InvalidOid,
                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                     errmsg("invalid input syntax for type %s: \"%s\"",
                            "oid", s)));
 #if OID_MAX != ULONG_MAX
    if (cvt != (unsigned long) result &&
        cvt != (unsigned long) ((int) result))
-       ereport(ERROR,
+       ereturn(escontext, InvalidOid,
                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                 errmsg("value \"%s\" is out of range for type %s",
                        s, "oid")));
    char       *s = PG_GETARG_CSTRING(0);
    Oid         result;
 
-   result = oidin_subr(s, NULL);
+   result = oidin_subr(s, NULL, fcinfo->context);
    PG_RETURN_OID(result);
 }
 
 oidvectorin(PG_FUNCTION_ARGS)
 {
    char       *oidString = PG_GETARG_CSTRING(0);
+   Node       *escontext = fcinfo->context;
    oidvector  *result;
    int         n;
 
            oidString++;
        if (*oidString == '\0')
            break;
-       result->values[n] = oidin_subr(oidString, &oidString);
+       result->values[n] = oidin_subr(oidString, &oidString, escontext);
+       if (SOFT_ERROR_OCCURRED(escontext))
+           PG_RETURN_NULL();
    }
    while (*oidString && isspace((unsigned char) *oidString))
        oidString++;
    if (*oidString)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("oidvector has too many elements")));
 
             * constants by the lexer.  Accept these if they are valid OID
             * strings.
             */
-           return oidin_subr(castNode(Float, node)->fval, NULL);
+           return oidin_subr(castNode(Float, node)->fval, NULL, NULL);
        default:
            elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
    }
 
 
    result = pg_lsn_in_internal(str, &have_error);
    if (have_error)
-       ereport(ERROR,
+       ereturn(fcinfo->context, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "pg_lsn", str)));
 
 tidin(PG_FUNCTION_ARGS)
 {
    char       *str = PG_GETARG_CSTRING(0);
+   Node       *escontext = fcinfo->context;
    char       *p,
               *coord[NTIDARGS];
    int         i;
            coord[i++] = p + 1;
 
    if (i < NTIDARGS)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "tid", str)));
    errno = 0;
    cvt = strtoul(coord[0], &badp, 10);
    if (errno || *badp != DELIM)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "tid", str)));
 #if SIZEOF_LONG > 4
    if (cvt != (unsigned long) blockNumber &&
        cvt != (unsigned long) ((int32) blockNumber))
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "tid", str)));
    cvt = strtoul(coord[1], &badp, 10);
    if (errno || *badp != RDELIM ||
        cvt > USHRT_MAX)
-       ereport(ERROR,
+       ereturn(escontext, (Datum) 0,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "tid", str)));
 
  * parse snapshot from cstring
  */
 static pg_snapshot *
-parse_snapshot(const char *str)
+parse_snapshot(const char *str, Node *escontext)
 {
    FullTransactionId xmin;
    FullTransactionId xmax;
    return buf_finalize(buf);
 
 bad_format:
-   ereport(ERROR,
+   ereturn(escontext, NULL,
            (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
             errmsg("invalid input syntax for type %s: \"%s\"",
                    "pg_snapshot", str_start)));
-   return NULL;                /* keep compiler quiet */
 }
 
 /*
    char       *str = PG_GETARG_CSTRING(0);
    pg_snapshot *snap;
 
-   snap = parse_snapshot(str);
+   snap = parse_snapshot(str, fcinfo->context);
 
    PG_RETURN_POINTER(snap);
 }
 
  value "50000" is out of range for type smallint
 (1 row)
 
+-- While we're here, check int2vector as well
+SELECT pg_input_is_valid(' 1 3  5 ', 'int2vector');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_error_message('1 asdf', 'int2vector');
+             pg_input_error_message             
+------------------------------------------------
+ invalid input syntax for type smallint: "asdf"
+(1 row)
+
+SELECT pg_input_error_message('50000', 'int2vector');
+             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);
 
          15
 (8 rows)
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('1234', 'oid');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('01XYZ', 'oid');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('01XYZ', 'oid');
+           pg_input_error_message           
+--------------------------------------------
+ invalid input syntax for type oid: "01XYZ"
+(1 row)
+
+SELECT pg_input_is_valid('9999999999', 'oid');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('9999999999', 'oid');
+             pg_input_error_message              
+-------------------------------------------------
+ value "9999999999" is out of range for type oid
+(1 row)
+
+-- While we're here, check oidvector as well
+SELECT pg_input_is_valid(' 1 2  4 ', 'oidvector');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('01 01XYZ', 'oidvector');
+          pg_input_error_message          
+------------------------------------------
+ invalid input syntax for type oid: "XYZ"
+(1 row)
+
+SELECT pg_input_is_valid('01 9999999999', 'oidvector');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('01 9999999999', 'oidvector');
+             pg_input_error_message              
+-------------------------------------------------
+ value "9999999999" is out of range for type oid
+(1 row)
+
 SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
   f1  
 ------
 
 ERROR:  invalid input syntax for type pg_lsn: "/ABCD"
 LINE 1: INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
                                        ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
+             pg_input_error_message              
+-------------------------------------------------
+ invalid input syntax for type pg_lsn: "16AE7F7"
+(1 row)
+
 -- Min/Max aggregation
 SELECT MIN(f1), MAX(f1) FROM PG_LSN_TBL;
  min |        max        
 
 SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole,
    'SELECT, fake_privilege', FALSE);  -- error
 ERROR:  unrecognized privilege type: "fake_privilege"
+-- Test non-throwing aclitem I/O
+SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
+     pg_input_error_message      
+---------------------------------
+ a name must follow the "/" sign
+(1 row)
+
+SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+           pg_input_error_message           
+--------------------------------------------
+ role "regress_no_such_user" does not exist
+(1 row)
+
+SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
+                  pg_input_error_message                  
+----------------------------------------------------------
+ invalid mode character: must be one of "arwdDxtXUCTcsAm"
+(1 row)
+
 --
 -- Testing blanket default grants is very hazardous since it might change
 -- the privileges attached to objects created by concurrent regression tests.
 
 ERROR:  invalid input syntax for type tid: "(1,65536)"
 LINE 1: SELECT '(1,65536)'::tid;
                ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('(0)', 'tid');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('(0)', 'tid');
+          pg_input_error_message          
+------------------------------------------
+ invalid input syntax for type tid: "(0)"
+(1 row)
+
+SELECT pg_input_is_valid('(0,-1)', 'tid');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('(0,-1)', 'tid');
+           pg_input_error_message            
+---------------------------------------------
+ invalid input syntax for type tid: "(0,-1)"
+(1 row)
+
 -- tests for functions related to TID handling
 CREATE TABLE tid_tab (a int);
 -- min() and max() for TIDs
 
 ERROR:  invalid input syntax for type pg_snapshot: "12:16:14,13"
 LINE 1: select '12:16:14,13'::pg_snapshot;
                ^
+-- also try it with non-error-throwing API
+select pg_input_is_valid('12:13:', 'pg_snapshot');
+ pg_input_is_valid 
+-------------------
+ t
+(1 row)
+
+select pg_input_is_valid('31:12:', 'pg_snapshot');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('31:12:', 'pg_snapshot');
+               pg_input_error_message                
+-----------------------------------------------------
+ invalid input syntax for type pg_snapshot: "31:12:"
+(1 row)
+
+select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
+ pg_input_is_valid 
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('12:16:14,13', 'pg_snapshot');
+                  pg_input_error_message                  
+----------------------------------------------------------
+ invalid input syntax for type pg_snapshot: "12:16:14,13"
+(1 row)
+
 create temp table snapshot_test (
    nr  integer,
    snap    pg_snapshot
 
 SELECT pg_input_is_valid('50000', 'int2');
 SELECT pg_input_error_message('50000', 'int2');
 
+-- While we're here, check int2vector as well
+SELECT pg_input_is_valid(' 1 3  5 ', 'int2vector');
+SELECT pg_input_error_message('1 asdf', 'int2vector');
+SELECT pg_input_error_message('50000', 'int2vector');
+
 SELECT * FROM INT2_TBL AS f(a, b);
 
 SELECT * FROM (TABLE int2_tbl) AS s (a, b);
 
 
 SELECT * FROM OID_TBL;
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('1234', 'oid');
+SELECT pg_input_is_valid('01XYZ', 'oid');
+SELECT pg_input_error_message('01XYZ', 'oid');
+SELECT pg_input_is_valid('9999999999', 'oid');
+SELECT pg_input_error_message('9999999999', 'oid');
+
+-- While we're here, check oidvector as well
+SELECT pg_input_is_valid(' 1 2  4 ', 'oidvector');
+SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
+SELECT pg_input_error_message('01 01XYZ', 'oidvector');
+SELECT pg_input_is_valid('01 9999999999', 'oidvector');
+SELECT pg_input_error_message('01 9999999999', 'oidvector');
+
 SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
 
 SELECT o.* FROM OID_TBL o WHERE o.f1 <> '1234';
 
 INSERT INTO PG_LSN_TBL VALUES ('ABCD/');
 INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
+SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
+
 -- Min/Max aggregation
 SELECT MIN(f1), MAX(f1) FROM PG_LSN_TBL;
 
 
 SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole,
    'SELECT, fake_privilege', FALSE);  -- error
 
+-- Test non-throwing aclitem I/O
+SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem');
+SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
+SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
+SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
+SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
+
 --
 -- Testing blanket default grants is very hazardous since it might change
 -- the privileges attached to objects created by concurrent regression tests.
 
 SELECT '(4294967296,1)'::tid;  -- error
 SELECT '(1,65536)'::tid;  -- error
 
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('(0)', 'tid');
+SELECT pg_input_error_message('(0)', 'tid');
+SELECT pg_input_is_valid('(0,-1)', 'tid');
+SELECT pg_input_error_message('(0,-1)', 'tid');
+
 
 -- tests for functions related to TID handling
 
 
 select '12:13:0'::pg_snapshot;
 select '12:16:14,13'::pg_snapshot;
 
+-- also try it with non-error-throwing API
+select pg_input_is_valid('12:13:', 'pg_snapshot');
+select pg_input_is_valid('31:12:', 'pg_snapshot');
+select pg_input_error_message('31:12:', 'pg_snapshot');
+select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
+select pg_input_error_message('12:16:14,13', 'pg_snapshot');
+
 create temp table snapshot_test (
    nr  integer,
    snap    pg_snapshot