]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Convert jsonpath's input function to report errors softly
authorAndrew Dunstan <andrew@dunslane.net>
Sat, 24 Dec 2022 20:19:14 +0000 (15:19 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Sat, 24 Dec 2022 20:21:20 +0000 (15:21 -0500)
Reviewed by Tom Lane

Discussion: https://postgr.es/m/a8dc5700-c341-3ba8-0507-cc09881e6200@dunslane.net

src/backend/utils/adt/jsonpath.c
src/backend/utils/adt/jsonpath_exec.c
src/backend/utils/adt/jsonpath_gram.y
src/backend/utils/adt/jsonpath_internal.h
src/backend/utils/adt/jsonpath_scan.l
src/include/utils/jsonpath.h
src/test/regress/expected/jsonpath.out
src/test/regress/sql/jsonpath.sql

index 91af030095239f562e065d00626b89e30aabceed..bb9364843e6b506d08a34262266b2a1d99234bc5 100644 (file)
 #include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/json.h"
 #include "utils/jsonpath.h"
 
 
-static Datum jsonPathFromCstring(char *in, int len);
+static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
 static char *jsonPathToCstring(StringInfo out, JsonPath *in,
                                                           int estimated_len);
-static int     flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+static bool    flattenJsonPathParseItem(StringInfo buf, int *result,
+                                                                        struct Node *escontext,
+                                                                        JsonPathParseItem *item,
                                                                         int nestingLevel, bool insideArraySubscript);
 static void alignStringInfoInt(StringInfo buf);
 static int32 reserveSpaceForItemPointer(StringInfo buf);
@@ -95,7 +98,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
        char       *in = PG_GETARG_CSTRING(0);
        int                     len = strlen(in);
 
-       return jsonPathFromCstring(in, len);
+       return jsonPathFromCstring(in, len, fcinfo->context);
 }
 
 /*
@@ -119,7 +122,7 @@ jsonpath_recv(PG_FUNCTION_ARGS)
        else
                elog(ERROR, "unsupported jsonpath version number: %d", version);
 
-       return jsonPathFromCstring(str, nbytes);
+       return jsonPathFromCstring(str, nbytes, NULL);
 }
 
 /*
@@ -165,24 +168,29 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * representation of jsonpath.
  */
 static Datum
-jsonPathFromCstring(char *in, int len)
+jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
-       JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+       JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
        JsonPath   *res;
        StringInfoData buf;
 
-       initStringInfo(&buf);
-       enlargeStringInfo(&buf, 4 * len /* estimation */ );
-
-       appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+       if (SOFT_ERROR_OCCURRED(escontext))
+               return (Datum) 0;
 
        if (!jsonpath)
-               ereport(ERROR,
+               ereturn(escontext, (Datum) 0,
                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
                                                in)));
 
-       flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
+       initStringInfo(&buf);
+       enlargeStringInfo(&buf, 4 * len /* estimation */ );
+
+       appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+       if (!flattenJsonPathParseItem(&buf, NULL, escontext,
+                                                                 jsonpath->expr, 0, false))
+               return (Datum) 0;
 
        res = (JsonPath *) buf.data;
        SET_VARSIZE(res, buf.len);
@@ -225,9 +233,10 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
  * Recursive function converting given jsonpath parse item and all its
  * children into a binary representation.
  */
-static int
-flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
-                                                int nestingLevel, bool insideArraySubscript)
+static bool
+flattenJsonPathParseItem(StringInfo buf,  int *result, struct Node *escontext,
+                                                JsonPathParseItem *item, int nestingLevel,
+                                                bool insideArraySubscript)
 {
        /* position from beginning of jsonpath data */
        int32           pos = buf->len - JSONPATH_HDRSZ;
@@ -295,16 +304,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
                                int32           left = reserveSpaceForItemPointer(buf);
                                int32           right = reserveSpaceForItemPointer(buf);
 
-                               chld = !item->value.args.left ? pos :
-                                       flattenJsonPathParseItem(buf, item->value.args.left,
-                                                                                        nestingLevel + argNestingLevel,
-                                                                                        insideArraySubscript);
+                               if (!item->value.args.left)
+                                       chld = pos;
+                               else if (! flattenJsonPathParseItem(buf, &chld, escontext,
+                                                                                                       item->value.args.left,
+                                                                                                       nestingLevel + argNestingLevel,
+                                                                                                       insideArraySubscript))
+                                       return false;
                                *(int32 *) (buf->data + left) = chld - pos;
 
-                               chld = !item->value.args.right ? pos :
-                                       flattenJsonPathParseItem(buf, item->value.args.right,
-                                                                                        nestingLevel + argNestingLevel,
-                                                                                        insideArraySubscript);
+                               if (!item->value.args.right)
+                                       chld = pos;
+                               else if (! flattenJsonPathParseItem(buf, &chld, escontext,
+                                                                                                       item->value.args.right,
+                                                                                                       nestingLevel + argNestingLevel,
+                                                                                                       insideArraySubscript))
+                                       return false;
                                *(int32 *) (buf->data + right) = chld - pos;
                        }
                        break;
@@ -323,9 +338,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
                                                                           item->value.like_regex.patternlen);
                                appendStringInfoChar(buf, '\0');
 
-                               chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
-                                                                                               nestingLevel,
-                                                                                               insideArraySubscript);
+                               if (! flattenJsonPathParseItem(buf, &chld, escontext,
+                                                                                          item->value.like_regex.expr,
+                                                                                          nestingLevel,
+                                                                                          insideArraySubscript))
+                                       return false;
                                *(int32 *) (buf->data + offs) = chld - pos;
                        }
                        break;
@@ -341,10 +358,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
                        {
                                int32           arg = reserveSpaceForItemPointer(buf);
 
-                               chld = !item->value.arg ? pos :
-                                       flattenJsonPathParseItem(buf, item->value.arg,
-                                                                                        nestingLevel + argNestingLevel,
-                                                                                        insideArraySubscript);
+                               if (!item->value.arg)
+                                       chld = pos;
+                               else if (! flattenJsonPathParseItem(buf, &chld, escontext,
+                                                                                                       item->value.arg,
+                                                                                                       nestingLevel + argNestingLevel,
+                                                                                                       insideArraySubscript))
+                                       return false;
                                *(int32 *) (buf->data + arg) = chld - pos;
                        }
                        break;
@@ -357,13 +377,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
                        break;
                case jpiCurrent:
                        if (nestingLevel <= 0)
-                               ereport(ERROR,
+                               ereturn(escontext, false,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("@ is not allowed in root expressions")));
                        break;
                case jpiLast:
                        if (!insideArraySubscript)
-                               ereport(ERROR,
+                               ereturn(escontext, false,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("LAST is allowed only in array subscripts")));
                        break;
@@ -383,15 +403,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
                                {
                                        int32      *ppos;
                                        int32           topos;
-                                       int32           frompos =
-                                       flattenJsonPathParseItem(buf,
-                                                                                        item->value.array.elems[i].from,
-                                                                                        nestingLevel, true) - pos;
+                                       int32           frompos;
+
+                                       if (! flattenJsonPathParseItem(buf, &frompos, escontext,
+                                                                                                  item->value.array.elems[i].from,
+                                                                                                  nestingLevel, true))
+                                               return false;
+                                       frompos -= pos;
 
                                        if (item->value.array.elems[i].to)
-                                               topos = flattenJsonPathParseItem(buf,
-                                                                                                                item->value.array.elems[i].to,
-                                                                                                                nestingLevel, true) - pos;
+                                       {
+                                               if (! flattenJsonPathParseItem(buf, &topos, escontext,
+                                                                                                          item->value.array.elems[i].to,
+                                                                                                          nestingLevel, true))
+                                                       return false;
+                                               topos -= pos;
+                                       }
                                        else
                                                topos = 0;
 
@@ -424,12 +451,17 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 
        if (item->next)
        {
-               chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
-                                                                               insideArraySubscript) - pos;
+               if (! flattenJsonPathParseItem(buf, &chld, escontext,
+                                                                          item->next, nestingLevel,
+                                                                          insideArraySubscript))
+                       return false;
+               chld -= pos;
                *(int32 *) (buf->data + next) = chld;
        }
 
-       return pos;
+       if (result)
+               *result = pos;
+       return true;
 }
 
 /*
index e758616eb83687444e8e8e2df6b737988d02aea5..4335e1c28215f64a40b733d383e9dcde70b37129 100644 (file)
@@ -1721,7 +1721,8 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
                cxt->regex =
                        cstring_to_text_with_len(jsp->content.like_regex.pattern,
                                                                         jsp->content.like_regex.patternlen);
-               cxt->cflags = jspConvertRegexFlags(jsp->content.like_regex.flags);
+               (void) jspConvertRegexFlags(jsp->content.like_regex.flags,
+                                                                       &(cxt->cflags), NULL);
        }
 
        if (RE_compile_and_execute(cxt->regex, str->val.string.val,
index 2a56629cc3a12855140fc8e9dbf1380ac974a7c9..a460fa1bf75c0e15ba91dfb19cd4e985bf14acb9 100644 (file)
@@ -38,9 +38,11 @@ static JsonPathParseItem *makeItemUnary(JsonPathItemType type,
 static JsonPathParseItem *makeItemList(List *list);
 static JsonPathParseItem *makeIndexArray(List *list);
 static JsonPathParseItem *makeAny(int first, int last);
-static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
-                                                                                       JsonPathString *pattern,
-                                                                                       JsonPathString *flags);
+static bool makeItemLikeRegex(JsonPathParseItem *expr,
+                                                         JsonPathString *pattern,
+                                                         JsonPathString *flags,
+                                                         JsonPathParseItem ** result,
+                                                         struct Node *escontext);
 
 /*
  * Bison doesn't allocate anything that needs to live across parser calls,
@@ -57,6 +59,9 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %expect 0
 %name-prefix="jsonpath_yy"
 %parse-param {JsonPathParseResult **result}
+%parse-param {struct Node *escontext}
+%lex-param {JsonPathParseResult **result}
+%lex-param {struct Node *escontext}
 
 %union
 {
@@ -163,9 +168,20 @@ predicate:
                                                                        { $$ = makeItemUnary(jpiIsUnknown, $2); }
        | expr STARTS_P WITH_P starts_with_initial
                                                                        { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
-       | expr LIKE_REGEX_P STRING_P    { $$ = makeItemLikeRegex($1, &$3, NULL); }
+       | expr LIKE_REGEX_P STRING_P
+       {
+               JsonPathParseItem *jppitem;
+               if (! makeItemLikeRegex($1, &$3, NULL, &jppitem, escontext))
+                       YYABORT;
+               $$ = jppitem;
+       }
        | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
-                                                                       { $$ = makeItemLikeRegex($1, &$3, &$5); }
+       {
+               JsonPathParseItem *jppitem;
+               if (! makeItemLikeRegex($1, &$3, &$5, &jppitem, escontext))
+                       YYABORT;
+               $$ = jppitem;
+       }
        ;
 
 starts_with_initial:
@@ -472,9 +488,10 @@ makeAny(int first, int last)
        return v;
 }
 
-static JsonPathParseItem *
+static bool
 makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
-                                 JsonPathString *flags)
+                                 JsonPathString *flags, JsonPathParseItem ** result,
+                                 struct Node *escontext)
 {
        JsonPathParseItem *v = makeItemType(jpiLikeRegex);
        int                     i;
@@ -506,7 +523,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
                                v->value.like_regex.flags |= JSP_REGEX_QUOTE;
                                break;
                        default:
-                               ereport(ERROR,
+                               ereturn(escontext, false,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("invalid input syntax for type %s", "jsonpath"),
                                                 errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.",
@@ -515,22 +532,48 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
                }
        }
 
-       /* Convert flags to what RE_compile_and_cache needs */
-       cflags = jspConvertRegexFlags(v->value.like_regex.flags);
+       /* Convert flags to what pg_regcomp needs */
+       if ( !jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext))
+                return false;
 
        /* check regex validity */
-       (void) RE_compile_and_cache(cstring_to_text_with_len(pattern->val,
-                                                                                                                pattern->len),
-                                                               cflags, DEFAULT_COLLATION_OID);
+       {
+               regex_t     re_tmp;
+               pg_wchar   *wpattern;
+               int         wpattern_len;
+               int         re_result;
+
+               wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar));
+               wpattern_len = pg_mb2wchar_with_len(pattern->val,
+                                                                                       wpattern,
+                                                                                       pattern->len);
+
+               if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags,
+                                                                       DEFAULT_COLLATION_OID)) != REG_OKAY)
+               {
+                       char        errMsg[100];
+
+                       /* See regexp.c for explanation */
+                       CHECK_FOR_INTERRUPTS();
+                       pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg));
+                       ereturn(escontext, false,
+                                       (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                                        errmsg("invalid regular expression: %s", errMsg)));
+               }
 
-       return v;
+               pg_regfree(&re_tmp);
+       }
+
+       *result = v;
+
+       return true;
 }
 
 /*
  * Convert from XQuery regex flags to those recognized by our regex library.
  */
-int
-jspConvertRegexFlags(uint32 xflags)
+bool
+jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext)
 {
        /* By default, XQuery is very nearly the same as Spencer's AREs */
        int                     cflags = REG_ADVANCED;
@@ -561,18 +604,12 @@ jspConvertRegexFlags(uint32 xflags)
                 * XQuery-style ignore-whitespace mode.
                 */
                if (xflags & JSP_REGEX_WSPACE)
-                       ereport(ERROR,
+                       ereturn(escontext, false,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented")));
        }
 
-       /*
-        * We'll never need sub-match details at execution.  While
-        * RE_compile_and_execute would set this flag anyway, force it on here to
-        * ensure that the regex cache entries created by makeItemLikeRegex are
-        * useful.
-        */
-       cflags |= REG_NOSUB;
+       *result = cflags;
 
-       return cflags;
+       return true;
 }
index edfc6191a0e1669858c5719792112ea94958af00..b27716855e95da9c9c0dec6089811dc7dad64138 100644 (file)
@@ -25,8 +25,14 @@ typedef struct JsonPathString
 #include "utils/jsonpath.h"
 #include "jsonpath_gram.h"
 
-extern int     jsonpath_yylex(YYSTYPE *yylval_param);
-extern int     jsonpath_yyparse(JsonPathParseResult **result);
-extern void jsonpath_yyerror(JsonPathParseResult **result, const char *message);
+#define YY_DECL extern int     jsonpath_yylex(YYSTYPE *yylval_param, \
+                                                         JsonPathParseResult **result, \
+                                                         struct Node *escontext)
+YY_DECL;
+extern int     jsonpath_yyparse(JsonPathParseResult **result,
+                                                               struct Node *escontext);
+extern void jsonpath_yyerror(JsonPathParseResult **result,
+                                                        struct Node *escontext,
+                                                        const char *message);
 
 #endif                                                 /* JSONPATH_INTERNAL_H */
index 948f379e76781befb6383c9714727ebda6b5e211..59652c76dc7767b843386504aa3ad06f94471a67 100644 (file)
@@ -25,6 +25,7 @@
 #include "jsonpath_gram.h"
 
 #include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
 #include "nodes/pg_list.h"
 }
 
@@ -39,8 +40,8 @@ static int    scanbuflen;
 static void addstring(bool init, char *s, int l);
 static void addchar(bool init, char c);
 static enum yytokentype checkKeyword(void);
-static void parseUnicode(char *s, int l);
-static void parseHexChar(char *s);
+static bool parseUnicode(char *s, int l, struct Node *escontext);
+static bool parseHexChar(char *s, struct Node *escontext);
 
 /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
 #undef fprintf
@@ -147,25 +148,48 @@ hex_fail  \\x{hex_dig}{0,1}
 
 <xnq,xq,xvq>\\v                                { addchar(false, '\v'); }
 
-<xnq,xq,xvq>{unicode}+         { parseUnicode(yytext, yyleng); }
+<xnq,xq,xvq>{unicode}+         {
+                                                               if (!parseUnicode(yytext, yyleng, escontext))
+                                                                       yyterminate();
+                                                       }
 
-<xnq,xq,xvq>{hex_char}         { parseHexChar(yytext); }
+<xnq,xq,xvq>{hex_char}         {
+                                                               if (!parseHexChar(yytext, escontext))
+                                                                       yyterminate();
+                                                       }
 
-<xnq,xq,xvq>{unicode}*{unicodefail}    { jsonpath_yyerror(NULL, "invalid unicode sequence"); }
+<xnq,xq,xvq>{unicode}*{unicodefail} {
+                                                               jsonpath_yyerror(NULL, escontext,
+                                                                                                "invalid unicode sequence");
+                                                               yyterminate();
+                                                       }
 
-<xnq,xq,xvq>{hex_fail}         { jsonpath_yyerror(NULL, "invalid hex character sequence"); }
+<xnq,xq,xvq>{hex_fail}         {
+                                                               jsonpath_yyerror(NULL, escontext,
+                                                                                                "invalid hex character sequence");
+                                                               yyterminate();
+                                                       }
 
 <xnq,xq,xvq>{unicode}+\\       {
                                                                /* throw back the \\, and treat as unicode */
                                                                yyless(yyleng - 1);
-                                                               parseUnicode(yytext, yyleng);
+                                                               if (!parseUnicode(yytext, yyleng, escontext))
+                                                                       yyterminate();
                                                        }
 
 <xnq,xq,xvq>\\.                                { addchar(false, yytext[1]); }
 
-<xnq,xq,xvq>\\                         { jsonpath_yyerror(NULL, "unexpected end after backslash"); }
+<xnq,xq,xvq>\\                         {
+                                                         jsonpath_yyerror(NULL, escontext,
+                                                                                          "unexpected end after backslash");
+                                                         yyterminate();
+                                                       }
 
-<xq,xvq><<EOF>>                                { jsonpath_yyerror(NULL, "unexpected end of quoted string"); }
+<xq,xvq><<EOF>>                                {
+                                                         jsonpath_yyerror(NULL, escontext,
+                                                                                          "unexpected end of quoted string");
+                                                         yyterminate();
+                                                       }
 
 <xq>\"                                                 {
                                                                        yylval->str = scanstring;
@@ -187,8 +211,12 @@ hex_fail   \\x{hex_dig}{0,1}
 
 <xc>\*                                                 { }
 
-<xc><<EOF>>                                            { jsonpath_yyerror(NULL, "unexpected end of comment"); }
-
+<xc><<EOF>>                                            {
+                                                                       jsonpath_yyerror(
+                                                                               NULL, escontext,
+                                                                               "unexpected end of comment");
+                                                                       yyterminate();
+                                                               }
 \&\&                                                   { return AND_P; }
 
 \|\|                                                   { return OR_P; }
@@ -253,11 +281,30 @@ hex_fail  \\x{hex_dig}{0,1}
                                                                        return INT_P;
                                                                }
 
-{realfail}                                             { jsonpath_yyerror(NULL, "invalid numeric literal"); }
-{integer_junk}                                 { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); }
-{decimal_junk}                                 { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); }
-{real_junk}                                            { jsonpath_yyerror(NULL, "trailing junk after numeric literal"); }
-
+{realfail}                                             {
+                                                                       jsonpath_yyerror(
+                                                                               NULL, escontext,
+                                                                               "invalid numeric literal");
+                                                                       yyterminate();
+                                                               }
+{integer_junk}                                 {
+                                                                       jsonpath_yyerror(
+                                                                               NULL, escontext,
+                                                                               "trailing junk after numeric literal");
+                                                                       yyterminate();
+                                                               }
+{decimal_junk}                                 {
+                                                                       jsonpath_yyerror(
+                                                                               NULL, escontext,
+                                                                               "trailing junk after numeric literal");
+                                                                       yyterminate();
+                                                               }
+{real_junk}                                            {
+                                                                       jsonpath_yyerror(
+                                                                               NULL, escontext,
+                                                                               "trailing junk after numeric literal");
+                                                                       yyterminate();
+                                                               }
 \"                                                             {
                                                                        addchar(true, '\0');
                                                                        BEGIN xq;
@@ -281,18 +328,23 @@ hex_fail  \\x{hex_dig}{0,1}
 /* LCOV_EXCL_STOP */
 
 void
-jsonpath_yyerror(JsonPathParseResult **result, const char *message)
+jsonpath_yyerror(JsonPathParseResult **result, struct Node *escontext,
+                                const char *message)
 {
+       /* don't overwrite escontext if it's already been set */
+       if (SOFT_ERROR_OCCURRED(escontext))
+               return;
+
        if (*yytext == YY_END_OF_BUFFER_CHAR)
        {
-               ereport(ERROR,
+               errsave(escontext,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 /* translator: %s is typically "syntax error" */
                                 errmsg("%s at end of jsonpath input", _(message))));
        }
        else
        {
-               ereport(ERROR,
+               errsave(escontext,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 /* translator: first %s is typically "syntax error" */
                                 errmsg("%s at or near \"%s\" of jsonpath input",
@@ -463,14 +515,14 @@ addchar(bool init, char c)
 
 /* Interface to jsonpath parser */
 JsonPathParseResult *
-parsejsonpath(const char *str, int len)
+parsejsonpath(const char *str, int len, struct Node *escontext)
 {
        JsonPathParseResult     *parseresult;
 
        jsonpath_scanner_init(str, len);
 
-       if (jsonpath_yyparse((void *) &parseresult) != 0)
-               jsonpath_yyerror(NULL, "bogus input"); /* shouldn't happen */
+       if (jsonpath_yyparse((void *) &parseresult, escontext) != 0)
+               jsonpath_yyerror(NULL, escontext, "bogus input"); /* shouldn't happen */
 
        jsonpath_scanner_finish();
 
@@ -478,27 +530,36 @@ parsejsonpath(const char *str, int len)
 }
 
 /* Turn hex character into integer */
-static int
-hexval(char c)
+static bool
+hexval(char c, int *result, struct Node *escontext)
 {
        if (c >= '0' && c <= '9')
-               return c - '0';
+       {
+               *result = c - '0';
+               return true;
+       }
        if (c >= 'a' && c <= 'f')
-               return c - 'a' + 0xA;
+       {
+               *result = c - 'a' + 0xA;
+               return true;
+       }
        if (c >= 'A' && c <= 'F')
-               return c - 'A' + 0xA;
-       jsonpath_yyerror(NULL, "invalid hexadecimal digit");
-       return 0; /* not reached */
+       {
+               *result = c - 'A' + 0xA;
+               return true;
+       }
+       jsonpath_yyerror(NULL, escontext, "invalid hexadecimal digit");
+       return false;
 }
 
 /* Add given unicode character to scanstring */
-static void
-addUnicodeChar(int ch)
+static bool
+addUnicodeChar(int ch, struct Node *escontext)
 {
        if (ch == 0)
        {
                /* We can't allow this, since our TEXT type doesn't */
-               ereport(ERROR,
+               ereturn(escontext, false,
                                (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
                                 errmsg("unsupported Unicode escape sequence"),
                                  errdetail("\\u0000 cannot be converted to text.")));
@@ -507,30 +568,42 @@ addUnicodeChar(int ch)
        {
                char            cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1];
 
-               pg_unicode_to_server(ch, (unsigned char *) cbuf);
+               /*
+                * If we're trapping the error status, call the noerror form of the
+                * conversion function. Otherwise call the normal form which provides
+                * more detailed errors.
+                */
+
+               if (! escontext  || ! IsA(escontext, ErrorSaveContext))
+                       pg_unicode_to_server(ch, (unsigned char *) cbuf);
+               else if (!pg_unicode_to_server_noerror(ch, (unsigned char *) cbuf))
+                       ereturn(escontext, false,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("could not convert unicode to server encoding")));
                addstring(false, cbuf, strlen(cbuf));
        }
+       return true;
 }
 
 /* Add unicode character, processing any surrogate pairs */
-static void
-addUnicode(int ch, int *hi_surrogate)
+static bool
+addUnicode(int ch, int *hi_surrogate, struct Node *escontext)
 {
        if (is_utf16_surrogate_first(ch))
        {
                if (*hi_surrogate != -1)
-                       ereport(ERROR,
+                       ereturn(escontext, false,
                                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                         errmsg("invalid input syntax for type %s", "jsonpath"),
                                         errdetail("Unicode high surrogate must not follow "
                                                           "a high surrogate.")));
                *hi_surrogate = ch;
-               return;
+               return true;
        }
        else if (is_utf16_surrogate_second(ch))
        {
                if (*hi_surrogate == -1)
-                       ereport(ERROR,
+                       ereturn(escontext, false,
                                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                         errmsg("invalid input syntax for type %s", "jsonpath"),
                                         errdetail("Unicode low surrogate must follow a high "
@@ -540,22 +613,22 @@ addUnicode(int ch, int *hi_surrogate)
        }
        else if (*hi_surrogate != -1)
        {
-               ereport(ERROR,
+               ereturn(escontext, false,
                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                 errmsg("invalid input syntax for type %s", "jsonpath"),
                                 errdetail("Unicode low surrogate must follow a high "
                                                   "surrogate.")));
        }
 
-       addUnicodeChar(ch);
+       return addUnicodeChar(ch, escontext);
 }
 
 /*
  * parseUnicode was adopted from json_lex_string() in
  * src/backend/utils/adt/json.c
  */
-static void
-parseUnicode(char *s, int l)
+static bool
+parseUnicode(char *s, int l, struct Node *escontext)
 {
        int                     i = 2;
        int                     hi_surrogate = -1;
@@ -563,41 +636,57 @@ parseUnicode(char *s, int l)
        for (i = 2; i < l; i += 2)      /* skip '\u' */
        {
                int                     ch = 0;
-               int                     j;
+               int                     j, si;
 
                if (s[i] == '{')        /* parse '\u{XX...}' */
                {
                        while (s[++i] != '}' && i < l)
-                               ch = (ch << 4) | hexval(s[i]);
+                       {
+                               if (!hexval(s[i], &si, escontext))
+                                       return false;
+                               ch = (ch << 4) | si;
+                       }
                        i++;    /* skip '}' */
                }
                else            /* parse '\uXXXX' */
                {
                        for (j = 0; j < 4 && i < l; j++)
-                               ch = (ch << 4) | hexval(s[i++]);
+                       {
+                               if (!hexval(s[i++], &si, escontext))
+                                       return false;
+                               ch = (ch << 4) | si;
+                       }
                }
 
-               addUnicode(ch, &hi_surrogate);
+               if (! addUnicode(ch, &hi_surrogate, escontext))
+                       return false;
        }
 
        if (hi_surrogate != -1)
        {
-               ereport(ERROR,
+               ereturn(escontext, false,
                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                 errmsg("invalid input syntax for type %s", "jsonpath"),
                                 errdetail("Unicode low surrogate must follow a high "
                                                   "surrogate.")));
        }
+
+       return true;
 }
 
 /* Parse sequence of hex-encoded characters */
-static void
-parseHexChar(char *s)
+static bool
+parseHexChar(char *s, struct Node *escontext)
 {
-       int                     ch = (hexval(s[2]) << 4) |
-                                         hexval(s[3]);
+       int s2, s3, ch;
+       if (!hexval(s[2], &s2, escontext))
+               return false;
+       if (!hexval(s[3], &s3, escontext))
+               return false;
+
+       ch = (s2 << 4) | s3;
 
-       addUnicodeChar(ch);
+       return addUnicodeChar(ch, escontext);
 }
 
 /*
index 13f60cdc09b1ac4377beea10879144d2c598e0ae..b5035ceb191aba5e0f15c14c6fa1e9df73c855ac 100644 (file)
@@ -254,8 +254,11 @@ typedef struct JsonPathParseResult
        bool            lax;
 } JsonPathParseResult;
 
-extern JsonPathParseResult *parsejsonpath(const char *str, int len);
+extern JsonPathParseResult *parsejsonpath(const char *str, int len,
+                                                                                 struct Node *escontext);
+
+extern bool jspConvertRegexFlags(uint32 xflags, int *result,
+                                                                struct Node *escontext);
 
-extern int     jspConvertRegexFlags(uint32 xflags);
 
 #endif
index fdaac58367af4f409c842417c45d77bf5f0d393a..ca0cdf1ab2ca30dbf44497fb1c0e6581057a7e17 100644 (file)
@@ -1032,3 +1032,21 @@ select '1?(2>3)'::jsonpath;
  (1)?(2 > 3)
 (1 row)
 
+-- test non-error-throwing API
+SELECT str as jsonpath,
+       pg_input_is_valid(str,'jsonpath') as ok,
+       pg_input_error_message(str,'jsonpath') as errmsg
+FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
+                  '$ ? (@ like_regex "pattern" flag "a")',
+                  '@ + 1',
+                  '00',
+                  '1a']) str;
+                 jsonpath                  | ok |                                errmsg                                 
+-------------------------------------------+----+-----------------------------------------------------------------------
+ $ ? (@ like_regex "pattern" flag "smixq") | t  | 
+ $ ? (@ like_regex "pattern" flag "a")     | f  | invalid input syntax for type jsonpath
+ @ + 1                                     | f  | @ is not allowed in root expressions
+ 00                                        | f  | trailing junk after numeric literal at or near "00" of jsonpath input
+ 1a                                        | f  | trailing junk after numeric literal at or near "1a" of jsonpath input
+(5 rows)
+
index d491714614acc68790e7346d55a3f07e572db8d4..99d21d2af7849bfd0f1018fe6407533dd468ff77 100644 (file)
@@ -187,3 +187,14 @@ select '1..e3'::jsonpath;
 select '(1.).e'::jsonpath;
 select '(1.).e3'::jsonpath;
 select '1?(2>3)'::jsonpath;
+
+-- test non-error-throwing API
+
+SELECT str as jsonpath,
+       pg_input_is_valid(str,'jsonpath') as ok,
+       pg_input_error_message(str,'jsonpath') as errmsg
+FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
+                  '$ ? (@ like_regex "pattern" flag "a")',
+                  '@ + 1',
+                  '00',
+                  '1a']) str;