Re: Error-safe user functions - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Error-safe user functions
Date
Msg-id 3824377.1672076822@sss.pgh.pa.us
Whole thread Raw
In response to Re: Error-safe user functions  (Andrew Dunstan <andrew@dunslane.net>)
Responses Re: Error-safe user functions  (Andrew Dunstan <andrew@dunslane.net>)
List pgsql-hackers
Here's a proposed patch for making tsvectorin and tsqueryin
report errors softly.  We have to take the changes down a
couple of levels of subroutines, but it's not hugely difficult.

A couple of points worthy of comment:

* To reduce API changes, I made the functions in
tsvector_parser.c and tsquery.c pass around the escontext pointer
in TSVectorParseState and TSQueryParserState respectively.
This is a little duplicative, but since those structs are private
within those files, there's no easy way to share the same
pointer except by adding it as a new parameter to all those
functions.  This also means that if any of the outside callers
of parse_tsquery (in to_tsany.c) wanted to do soft error handling
and wanted their custom PushFunctions to be able to report such
errors, they'd need to pass the escontext via their "opaque"
passthrough structs, making for yet a third copy.  Still,
I judged adding an extra parameter to dozens of functions wasn't
a better way.

* There are two places in tsquery parsing that emit nuisance
NOTICEs about empty queries.  I chose to suppress those when
soft error handling has been requested.  Maybe we should rethink
whether we want them at all?

With the other patches I've posted recently, this covers all
of the core datatype input functions.  There are still half
a dozen to tackle in contrib.

            regards, tom lane

diff --git a/src/backend/tsearch/to_tsany.c b/src/backend/tsearch/to_tsany.c
index edeffacc2d..c0030ddaec 100644
--- a/src/backend/tsearch/to_tsany.c
+++ b/src/backend/tsearch/to_tsany.c
@@ -594,7 +594,8 @@ to_tsquery_byid(PG_FUNCTION_ARGS)
     query = parse_tsquery(text_to_cstring(in),
                           pushval_morph,
                           PointerGetDatum(&data),
-                          0);
+                          0,
+                          NULL);

     PG_RETURN_TSQUERY(query);
 }
@@ -630,7 +631,8 @@ plainto_tsquery_byid(PG_FUNCTION_ARGS)
     query = parse_tsquery(text_to_cstring(in),
                           pushval_morph,
                           PointerGetDatum(&data),
-                          P_TSQ_PLAIN);
+                          P_TSQ_PLAIN,
+                          NULL);

     PG_RETURN_POINTER(query);
 }
@@ -667,7 +669,8 @@ phraseto_tsquery_byid(PG_FUNCTION_ARGS)
     query = parse_tsquery(text_to_cstring(in),
                           pushval_morph,
                           PointerGetDatum(&data),
-                          P_TSQ_PLAIN);
+                          P_TSQ_PLAIN,
+                          NULL);

     PG_RETURN_TSQUERY(query);
 }
@@ -704,7 +707,8 @@ websearch_to_tsquery_byid(PG_FUNCTION_ARGS)
     query = parse_tsquery(text_to_cstring(in),
                           pushval_morph,
                           PointerGetDatum(&data),
-                          P_TSQ_WEB);
+                          P_TSQ_WEB,
+                          NULL);

     PG_RETURN_TSQUERY(query);
 }
diff --git a/src/backend/utils/adt/tsquery.c b/src/backend/utils/adt/tsquery.c
index a206926042..1097294d55 100644
--- a/src/backend/utils/adt/tsquery.c
+++ b/src/backend/utils/adt/tsquery.c
@@ -16,6 +16,7 @@

 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_type.h"
 #include "tsearch/ts_utils.h"
@@ -58,10 +59,16 @@ typedef enum
 /*
  * get token from query string
  *
- * *operator is filled in with OP_* when return values is PT_OPR,
- * but *weight could contain a distance value in case of phrase operator.
- * *strval, *lenval and *weight are filled in when return value is PT_VAL
+ * All arguments except "state" are output arguments.
  *
+ * If return value is PT_OPR, then *operator is filled with an OP_* code
+ * and *weight will contain a distance value in case of phrase operator.
+ *
+ * If return value is PT_VAL, then *lenval, *strval, *weight, and *prefix
+ * are filled.
+ *
+ * If PT_ERR is returned then a soft error has occurred.  If state->escontext
+ * isn't already filled then this should be reported as a generic parse error.
  */
 typedef ts_tokentype (*ts_tokenizer) (TSQueryParserState state, int8 *operator,
                                       int *lenval, char **strval,
@@ -93,6 +100,9 @@ struct TSQueryParserStateData

     /* state for value's parser */
     TSVectorParseState valstate;
+
+    /* context object for soft errors - must match valstate's escontext */
+    Node       *escontext;
 };

 /*
@@ -194,7 +204,7 @@ parse_phrase_operator(TSQueryParserState pstate, int16 *distance)
                 if (ptr == endptr)
                     return false;
                 else if (errno == ERANGE || l < 0 || l > MAXENTRYPOS)
-                    ereport(ERROR,
+                    ereturn(pstate->escontext, false,
                             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                              errmsg("distance in phrase operator must be an integer value between zero and %d
inclusive",
                                     MAXENTRYPOS)));
@@ -301,10 +311,8 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
                 }
                 else if (t_iseq(state->buf, ':'))
                 {
-                    ereport(ERROR,
-                            (errcode(ERRCODE_SYNTAX_ERROR),
-                             errmsg("syntax error in tsquery: \"%s\"",
-                                    state->buffer)));
+                    /* generic syntax error message is fine */
+                    return PT_ERR;
                 }
                 else if (!t_isspace(state->buf))
                 {
@@ -320,12 +328,17 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
                         state->state = WAITOPERATOR;
                         return PT_VAL;
                     }
+                    else if (SOFT_ERROR_OCCURRED(state->escontext))
+                    {
+                        /* gettoken_tsvector reported a soft error */
+                        return PT_ERR;
+                    }
                     else if (state->state == WAITFIRSTOPERAND)
                     {
                         return PT_END;
                     }
                     else
-                        ereport(ERROR,
+                        ereturn(state->escontext, PT_ERR,
                                 (errcode(ERRCODE_SYNTAX_ERROR),
                                  errmsg("no operand in tsquery: \"%s\"",
                                         state->buffer)));
@@ -354,6 +367,11 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
                     *operator = OP_PHRASE;
                     return PT_OPR;
                 }
+                else if (SOFT_ERROR_OCCURRED(state->escontext))
+                {
+                    /* parse_phrase_operator reported a soft error */
+                    return PT_ERR;
+                }
                 else if (t_iseq(state->buf, ')'))
                 {
                     state->buf++;
@@ -438,6 +456,11 @@ gettoken_query_websearch(TSQueryParserState state, int8 *operator,
                         state->state = WAITOPERATOR;
                         return PT_VAL;
                     }
+                    else if (SOFT_ERROR_OCCURRED(state->escontext))
+                    {
+                        /* gettoken_tsvector reported a soft error */
+                        return PT_ERR;
+                    }
                     else if (state->state == WAITFIRSTOPERAND)
                     {
                         return PT_END;
@@ -529,12 +552,12 @@ pushValue_internal(TSQueryParserState state, pg_crc32 valcrc, int distance, int
     QueryOperand *tmp;

     if (distance >= MAXSTRPOS)
-        ereport(ERROR,
+        ereturn(state->escontext,,
                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                  errmsg("value is too big in tsquery: \"%s\"",
                         state->buffer)));
     if (lenval >= MAXSTRLEN)
-        ereport(ERROR,
+        ereturn(state->escontext,,
                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                  errmsg("operand is too long in tsquery: \"%s\"",
                         state->buffer)));
@@ -562,7 +585,7 @@ pushValue(TSQueryParserState state, char *strval, int lenval, int16 weight, bool
     pg_crc32    valcrc;

     if (lenval >= MAXSTRLEN)
-        ereport(ERROR,
+        ereturn(state->escontext,,
                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                  errmsg("word is too long in tsquery: \"%s\"",
                         state->buffer)));
@@ -686,11 +709,17 @@ makepol(TSQueryParserState state,
                 return;
             case PT_ERR:
             default:
-                ereport(ERROR,
-                        (errcode(ERRCODE_SYNTAX_ERROR),
-                         errmsg("syntax error in tsquery: \"%s\"",
-                                state->buffer)));
+                /* don't overwrite a soft error saved by gettoken function */
+                if (!SOFT_ERROR_OCCURRED(state->escontext))
+                    errsave(state->escontext,
+                            (errcode(ERRCODE_SYNTAX_ERROR),
+                             errmsg("syntax error in tsquery: \"%s\"",
+                                    state->buffer)));
+                return;
         }
+        /* detect soft error in pushval or recursion */
+        if (SOFT_ERROR_OCCURRED(state->escontext))
+            return;
     }

     cleanOpStack(state, opstack, &lenstack, OP_OR /* lowest */ );
@@ -769,6 +798,8 @@ findoprnd(QueryItem *ptr, int size, bool *needcleanup)


 /*
+ * Parse the tsquery stored in "buf".
+ *
  * Each value (operand) in the query is passed to pushval. pushval can
  * transform the simple value to an arbitrarily complex expression using
  * pushValue and pushOperator. It must push a single value with pushValue,
@@ -778,12 +809,19 @@ findoprnd(QueryItem *ptr, int size, bool *needcleanup)
  *
  * opaque is passed on to pushval as is, pushval can use it to store its
  * private state.
+ *
+ * The pushval function can record soft errors via escontext.
+ * Callers must check SOFT_ERROR_OCCURRED to detect that.
+ *
+ * A bitmask of flags (see ts_utils.h) and an error context object
+ * can be provided as well.  If a soft error occurs, NULL is returned.
  */
 TSQuery
 parse_tsquery(char *buf,
               PushFunction pushval,
               Datum opaque,
-              int flags)
+              int flags,
+              Node *escontext)
 {
     struct TSQueryParserStateData state;
     int            i;
@@ -791,6 +829,7 @@ parse_tsquery(char *buf,
     int            commonlen;
     QueryItem  *ptr;
     ListCell   *cell;
+    bool        noisy;
     bool        needcleanup;
     int            tsv_flags = P_TSV_OPR_IS_DELIM | P_TSV_IS_TSQUERY;

@@ -808,15 +847,19 @@ parse_tsquery(char *buf,
     else
         state.gettoken = gettoken_query_standard;

+    /* emit nuisance NOTICEs only if not doing soft errors */
+    noisy = !(escontext && IsA(escontext, ErrorSaveContext));
+
     /* init state */
     state.buffer = buf;
     state.buf = buf;
     state.count = 0;
     state.state = WAITFIRSTOPERAND;
     state.polstr = NIL;
+    state.escontext = escontext;

     /* init value parser's state */
-    state.valstate = init_tsvector_parser(state.buffer, tsv_flags);
+    state.valstate = init_tsvector_parser(state.buffer, tsv_flags, escontext);

     /* init list of operand */
     state.sumlen = 0;
@@ -829,11 +872,15 @@ parse_tsquery(char *buf,

     close_tsvector_parser(state.valstate);

+    if (SOFT_ERROR_OCCURRED(escontext))
+        return NULL;
+
     if (state.polstr == NIL)
     {
-        ereport(NOTICE,
-                (errmsg("text-search query doesn't contain lexemes: \"%s\"",
-                        state.buffer)));
+        if (noisy)
+            ereport(NOTICE,
+                    (errmsg("text-search query doesn't contain lexemes: \"%s\"",
+                            state.buffer)));
         query = (TSQuery) palloc(HDRSIZETQ);
         SET_VARSIZE(query, HDRSIZETQ);
         query->size = 0;
@@ -841,7 +888,7 @@ parse_tsquery(char *buf,
     }

     if (TSQUERY_TOO_BIG(list_length(state.polstr), state.sumlen))
-        ereport(ERROR,
+        ereturn(escontext, NULL,
                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                  errmsg("tsquery is too large")));
     commonlen = COMPUTESIZE(list_length(state.polstr), state.sumlen);
@@ -889,7 +936,7 @@ parse_tsquery(char *buf,
      * If there are QI_VALSTOP nodes, delete them and simplify the tree.
      */
     if (needcleanup)
-        query = cleanup_tsquery_stopwords(query);
+        query = cleanup_tsquery_stopwords(query, noisy);

     return query;
 }
@@ -908,8 +955,13 @@ Datum
 tsqueryin(PG_FUNCTION_ARGS)
 {
     char       *in = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;

-    PG_RETURN_TSQUERY(parse_tsquery(in, pushval_asis, PointerGetDatum(NULL), 0));
+    PG_RETURN_TSQUERY(parse_tsquery(in,
+                                    pushval_asis,
+                                    PointerGetDatum(NULL),
+                                    0,
+                                    escontext));
 }

 /*
diff --git a/src/backend/utils/adt/tsquery_cleanup.c b/src/backend/utils/adt/tsquery_cleanup.c
index b77a7878dc..94030a75d5 100644
--- a/src/backend/utils/adt/tsquery_cleanup.c
+++ b/src/backend/utils/adt/tsquery_cleanup.c
@@ -383,7 +383,7 @@ calcstrlen(NODE *node)
  * Remove QI_VALSTOP (stopword) nodes from TSQuery.
  */
 TSQuery
-cleanup_tsquery_stopwords(TSQuery in)
+cleanup_tsquery_stopwords(TSQuery in, bool noisy)
 {
     int32        len,
                 lenstr,
@@ -403,8 +403,9 @@ cleanup_tsquery_stopwords(TSQuery in)
     root = clean_stopword_intree(maketree(GETQUERY(in)), &ladd, &radd);
     if (root == NULL)
     {
-        ereport(NOTICE,
-                (errmsg("text-search query contains only stop words or doesn't contain lexemes, ignored")));
+        if (noisy)
+            ereport(NOTICE,
+                    (errmsg("text-search query contains only stop words or doesn't contain lexemes, ignored")));
         out = palloc(HDRSIZETQ);
         out->size = 0;
         SET_VARSIZE(out, HDRSIZETQ);
diff --git a/src/backend/utils/adt/tsvector.c b/src/backend/utils/adt/tsvector.c
index 04c6f33537..0b430d3c47 100644
--- a/src/backend/utils/adt/tsvector.c
+++ b/src/backend/utils/adt/tsvector.c
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_utils.h"
 #include "utils/builtins.h"
@@ -178,6 +179,7 @@ Datum
 tsvectorin(PG_FUNCTION_ARGS)
 {
     char       *buf = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     TSVectorParseState state;
     WordEntryIN *arr;
     int            totallen;
@@ -201,7 +203,7 @@ tsvectorin(PG_FUNCTION_ARGS)
     char       *cur;
     int            buflen = 256;    /* allocated size of tmpbuf */

-    state = init_tsvector_parser(buf, 0);
+    state = init_tsvector_parser(buf, 0, escontext);

     arrlen = 64;
     arr = (WordEntryIN *) palloc(sizeof(WordEntryIN) * arrlen);
@@ -210,14 +212,14 @@ tsvectorin(PG_FUNCTION_ARGS)
     while (gettoken_tsvector(state, &token, &toklen, &pos, &poslen, NULL))
     {
         if (toklen >= MAXSTRLEN)
-            ereport(ERROR,
+            ereturn(escontext, (Datum) 0,
                     (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                      errmsg("word is too long (%ld bytes, max %ld bytes)",
                             (long) toklen,
                             (long) (MAXSTRLEN - 1))));

         if (cur - tmpbuf > MAXSTRPOS)
-            ereport(ERROR,
+            ereturn(escontext, (Datum) 0,
                     (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                      errmsg("string is too long for tsvector (%ld bytes, max %ld bytes)",
                             (long) (cur - tmpbuf), (long) MAXSTRPOS)));
@@ -261,13 +263,17 @@ tsvectorin(PG_FUNCTION_ARGS)

     close_tsvector_parser(state);

+    /* Did gettoken_tsvector fail? */
+    if (SOFT_ERROR_OCCURRED(escontext))
+        PG_RETURN_NULL();
+
     if (len > 0)
         len = uniqueentry(arr, len, tmpbuf, &buflen);
     else
         buflen = 0;

     if (buflen > MAXSTRPOS)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                  errmsg("string is too long for tsvector (%d bytes, max %d bytes)", buflen, MAXSTRPOS)));

@@ -285,6 +291,7 @@ tsvectorin(PG_FUNCTION_ARGS)
         stroff += arr[i].entry.len;
         if (arr[i].entry.haspos)
         {
+            /* This should be unreachable because of MAXNUMPOS restrictions */
             if (arr[i].poslen > 0xFFFF)
                 elog(ERROR, "positions array too long");

diff --git a/src/backend/utils/adt/tsvector_parser.c b/src/backend/utils/adt/tsvector_parser.c
index e2460d393a..eeea93e622 100644
--- a/src/backend/utils/adt/tsvector_parser.c
+++ b/src/backend/utils/adt/tsvector_parser.c
@@ -20,9 +20,19 @@

 /*
  * Private state of tsvector parser.  Note that tsquery also uses this code to
- * parse its input, hence the boolean flags.  The two flags are both true or
- * both false in current usage, but we keep them separate for clarity.
+ * parse its input, hence the boolean flags.  The oprisdelim and is_tsquery
+ * flags are both true or both false in current usage, but we keep them
+ * separate for clarity.
+ *
+ * If oprisdelim is set, the following characters are treated as delimiters
+ * (in addition to whitespace): ! | & ( )
+ *
  * is_tsquery affects *only* the content of error messages.
+ *
+ * is_web can be true to further modify tsquery parsing.
+ *
+ * If escontext is an ErrorSaveContext node, then soft errors can be
+ * captured there rather than being thrown.
  */
 struct TSVectorParseStateData
 {
@@ -34,16 +44,17 @@ struct TSVectorParseStateData
     bool        oprisdelim;        /* treat ! | * ( ) as delimiters? */
     bool        is_tsquery;        /* say "tsquery" not "tsvector" in errors? */
     bool        is_web;            /* we're in websearch_to_tsquery() */
+    Node       *escontext;        /* for soft error reporting */
 };


 /*
- * Initializes parser for the input string. If oprisdelim is set, the
- * following characters are treated as delimiters in addition to whitespace:
- * ! | & ( )
+ * Initializes a parser state object for the given input string.
+ * A bitmask of flags (see ts_utils.h) and an error context object
+ * can be provided as well.
  */
 TSVectorParseState
-init_tsvector_parser(char *input, int flags)
+init_tsvector_parser(char *input, int flags, Node *escontext)
 {
     TSVectorParseState state;

@@ -56,12 +67,15 @@ init_tsvector_parser(char *input, int flags)
     state->oprisdelim = (flags & P_TSV_OPR_IS_DELIM) != 0;
     state->is_tsquery = (flags & P_TSV_IS_TSQUERY) != 0;
     state->is_web = (flags & P_TSV_IS_WEB) != 0;
+    state->escontext = escontext;

     return state;
 }

 /*
  * Reinitializes parser to parse 'input', instead of previous input.
+ *
+ * Note that bufstart (the string reported in errors) is not changed.
  */
 void
 reset_tsvector_parser(TSVectorParseState state, char *input)
@@ -122,23 +136,26 @@ do { \
 #define WAITPOSDELIM    7
 #define WAITCHARCMPLX    8

-#define PRSSYNTAXERROR prssyntaxerror(state)
+#define PRSSYNTAXERROR return prssyntaxerror(state)

-static void
+static bool
 prssyntaxerror(TSVectorParseState state)
 {
-    ereport(ERROR,
+    errsave(state->escontext,
             (errcode(ERRCODE_SYNTAX_ERROR),
              state->is_tsquery ?
              errmsg("syntax error in tsquery: \"%s\"", state->bufstart) :
              errmsg("syntax error in tsvector: \"%s\"", state->bufstart)));
+    /* In soft error situation, return false as convenience for caller */
+    return false;
 }


 /*
  * Get next token from string being parsed. Returns true if successful,
- * false if end of input string is reached.  On success, these output
- * parameters are filled in:
+ * false if end of input string is reached or soft error.
+ *
+ * On success, these output parameters are filled in:
  *
  * *strval        pointer to token
  * *lenval        length of *strval
@@ -149,7 +166,11 @@ prssyntaxerror(TSVectorParseState state)
  * *poslen        number of elements in *pos_ptr
  * *endptr        scan resumption point
  *
- * Pass NULL for unwanted output parameters.
+ * Pass NULL for any unwanted output parameters.
+ *
+ * If state->escontext is an ErrorSaveContext, then caller must check
+ * SOFT_ERROR_OCCURRED() to determine whether a "false" result means
+ * error or normal end-of-string.
  */
 bool
 gettoken_tsvector(TSVectorParseState state,
@@ -195,7 +216,7 @@ gettoken_tsvector(TSVectorParseState state,
         else if (statecode == WAITNEXTCHAR)
         {
             if (*(state->prsbuf) == '\0')
-                ereport(ERROR,
+                ereturn(state->escontext, false,
                         (errcode(ERRCODE_SYNTAX_ERROR),
                          errmsg("there is no escaped character: \"%s\"",
                                 state->bufstart)));
@@ -313,7 +334,7 @@ gettoken_tsvector(TSVectorParseState state,
                 WEP_SETPOS(pos[npos - 1], LIMITPOS(atoi(state->prsbuf)));
                 /* we cannot get here in tsquery, so no need for 2 errmsgs */
                 if (WEP_GETPOS(pos[npos - 1]) == 0)
-                    ereport(ERROR,
+                    ereturn(state->escontext, false,
                             (errcode(ERRCODE_SYNTAX_ERROR),
                              errmsg("wrong position info in tsvector: \"%s\"",
                                     state->bufstart)));
diff --git a/src/include/tsearch/ts_utils.h b/src/include/tsearch/ts_utils.h
index 6fdd334fff..2297fb6cd5 100644
--- a/src/include/tsearch/ts_utils.h
+++ b/src/include/tsearch/ts_utils.h
@@ -25,11 +25,13 @@
 struct TSVectorParseStateData;    /* opaque struct in tsvector_parser.c */
 typedef struct TSVectorParseStateData *TSVectorParseState;

+/* flag bits that can be passed to init_tsvector_parser: */
 #define P_TSV_OPR_IS_DELIM    (1 << 0)
 #define P_TSV_IS_TSQUERY    (1 << 1)
 #define P_TSV_IS_WEB        (1 << 2)

-extern TSVectorParseState init_tsvector_parser(char *input, int flags);
+extern TSVectorParseState init_tsvector_parser(char *input, int flags,
+                                               Node *escontext);
 extern void reset_tsvector_parser(TSVectorParseState state, char *input);
 extern bool gettoken_tsvector(TSVectorParseState state,
                               char **strval, int *lenval,
@@ -58,13 +60,15 @@ typedef void (*PushFunction) (Datum opaque, TSQueryParserState state,
                                                      * QueryOperand struct */
                               bool prefix);

+/* flag bits that can be passed to parse_tsquery: */
 #define P_TSQ_PLAIN        (1 << 0)
 #define P_TSQ_WEB        (1 << 1)

 extern TSQuery parse_tsquery(char *buf,
                              PushFunction pushval,
                              Datum opaque,
-                             int flags);
+                             int flags,
+                             Node *escontext);

 /* Functions for use by PushFunction implementations */
 extern void pushValue(TSQueryParserState state,
@@ -222,7 +226,7 @@ extern int32 tsCompareString(char *a, int lena, char *b, int lenb, bool prefix);
  * TSQuery Utilities
  */
 extern QueryItem *clean_NOT(QueryItem *ptr, int32 *len);
-extern TSQuery cleanup_tsquery_stopwords(TSQuery in);
+extern TSQuery cleanup_tsquery_stopwords(TSQuery in, bool noisy);

 typedef struct QTNode
 {
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index 92c1c6e10b..a8785cd708 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -89,6 +89,25 @@ SELECT $$'' '1' '2'$$::tsvector;  -- error, empty lexeme is not allowed
 ERROR:  syntax error in tsvector: "'' '1' '2'"
 LINE 1: SELECT $$'' '1' '2'$$::tsvector;
                ^
+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('foo', 'tsvector');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid($$''$$, 'tsvector');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message($$''$$, 'tsvector');
+     pg_input_error_message
+--------------------------------
+ syntax error in tsvector: "''"
+(1 row)
+
 --Base tsquery test
 SELECT '1'::tsquery;
  tsquery
@@ -372,6 +391,31 @@ SELECT '!!a & !!b'::tsquery;
  !!'a' & !!'b'
 (1 row)

+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('foo', 'tsquery');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+SELECT pg_input_is_valid('foo!', 'tsquery');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+SELECT pg_input_error_message('foo!', 'tsquery');
+     pg_input_error_message
+---------------------------------
+ syntax error in tsquery: "foo!"
+(1 row)
+
+SELECT pg_input_error_message('a <100000> b', 'tsquery');
+                                pg_input_error_message
+---------------------------------------------------------------------------------------
+ distance in phrase operator must be an integer value between zero and 16384 inclusive
+(1 row)
+
 --comparisons
 SELECT 'a' < 'b & c'::tsquery as "true";
  true
diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql
index 61e8f49c91..b73dd1cb07 100644
--- a/src/test/regress/sql/tstypes.sql
+++ b/src/test/regress/sql/tstypes.sql
@@ -19,6 +19,11 @@ SELECT '''w'':4A,3B,2C,1D,5 a:8';
 SELECT 'a:3A b:2a'::tsvector || 'ba:1234 a:1B';
 SELECT $$'' '1' '2'$$::tsvector;  -- error, empty lexeme is not allowed

+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('foo', 'tsvector');
+SELECT pg_input_is_valid($$''$$, 'tsvector');
+SELECT pg_input_error_message($$''$$, 'tsvector');
+
 --Base tsquery test
 SELECT '1'::tsquery;
 SELECT '1 '::tsquery;
@@ -68,6 +73,12 @@ SELECT 'a & !!b'::tsquery;
 SELECT '!!a & b'::tsquery;
 SELECT '!!a & !!b'::tsquery;

+-- Also try it with non-error-throwing API
+SELECT pg_input_is_valid('foo', 'tsquery');
+SELECT pg_input_is_valid('foo!', 'tsquery');
+SELECT pg_input_error_message('foo!', 'tsquery');
+SELECT pg_input_error_message('a <100000> b', 'tsquery');
+
 --comparisons
 SELECT 'a' < 'b & c'::tsquery as "true";
 SELECT 'a' > 'b & c'::tsquery as "false";

pgsql-hackers by date:

Previous
From: Masahiko Sawada
Date:
Subject: Re: [PoC] Improve dead tuple storage for lazy vacuum
Next
From: Nikita Malakhov
Date:
Subject: Re: ARRNELEMS Out-of-bounds possible errors