Make JSON path numeric literals more correct
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 28 Mar 2022 08:41:43 +0000 (10:41 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 28 Mar 2022 09:11:39 +0000 (11:11 +0200)
Per ECMAScript standard (ECMA-262, referenced by SQL standard), the
syntax forms

.1
1.

should be allowed for decimal numeric literals, but the existing
implementation rejected them.

Also, by the same standard, reject trailing junk after numeric
literals.

Note that the ECMAScript standard for numeric literals is in respects
like these slightly different from the JSON standard, which might be
the original cause for this discrepancy.

A change is that this kind of syntax is now rejected:

    1.type()

This needs to be written as

    (1).type()

This is correct; normal JavaScript also does not accept this syntax.

We also need to fix up the jsonpath output function for this case.  We
put parentheses around numeric items if they are followed by another
path item.

Reviewed-by: Nikita Glukhov <n.gluhov@postgrespro.ru>
Discussion: https://www.postgresql.org/message-id/flat/50a828cc-0a00-7791-7883-2ed06dfb2dbb@enterprisedb.com

src/backend/utils/adt/jsonpath.c
src/backend/utils/adt/jsonpath_scan.l
src/test/regress/expected/jsonpath.out
src/test/regress/sql/jsonpath.sql

index 9be4e305ffa5fe22b607d020828b0176d902e652..91af030095239f562e065d00626b89e30aabceed 100644 (file)
@@ -500,9 +500,13 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
            escape_json(buf, jspGetString(v, NULL));
            break;
        case jpiNumeric:
+           if (jspHasNext(v))
+               appendStringInfoChar(buf, '(');
            appendStringInfoString(buf,
                                   DatumGetCString(DirectFunctionCall1(numeric_out,
                                                                       NumericGetDatum(jspGetNumeric(v)))));
+           if (jspHasNext(v))
+               appendStringInfoChar(buf, ')');
            break;
        case jpiBool:
            if (jspGetBool(v))
index 827a9e44cbbd8d973f8493c3555b55306123374d..1f08e7c51f897a5979510cecb1b19d62ce62b7e0 100644 (file)
@@ -82,11 +82,13 @@ other       [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
 
 digit      [0-9]
 integer        (0|[1-9]{digit}*)
-decimal        {integer}\.{digit}+
-decimalfail    {integer}\.
+decimal        ({integer}\.{digit}*|\.{digit}+)
 real       ({integer}|{decimal})[Ee][-+]?{digit}+
-realfail1  ({integer}|{decimal})[Ee]
-realfail2  ({integer}|{decimal})[Ee][-+]
+realfail   ({integer}|{decimal})[Ee][-+]
+
+integer_junk   {integer}{other}
+decimal_junk   {decimal}{other}
+real_junk      {real}{other}
 
 hex_dig        [0-9A-Fa-f]
 unicode        \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
@@ -242,16 +244,10 @@ hex_fail  \\x{hex_dig}{0,1}
                                    return INT_P;
                                }
 
-{decimalfail}                  {
-                                   /* throw back the ., and treat as integer */
-                                   yyless(yyleng - 1);
-                                   addstring(true, yytext, yyleng);
-                                   addchar(false, '\0');
-                                   yylval->str = scanstring;
-                                   return INT_P;
-                               }
-
-({realfail1}|{realfail2})      { yyerror(NULL, "invalid floating point number"); }
+{realfail}                     { yyerror(NULL, "invalid numeric literal"); }
+{integer_junk}                 { yyerror(NULL, "trailing junk after numeric literal"); }
+{decimal_junk}                 { yyerror(NULL, "trailing junk after numeric literal"); }
+{real_junk}                        { yyerror(NULL, "trailing junk after numeric literal"); }
 
 \"                             {
                                    addchar(true, '\0');
index e399fa963120168d8fad882efc461f87c14f8e6c..88eb22a4e9c97fe3df52e8d89f244ad31f45a8c8 100644 (file)
@@ -354,21 +354,19 @@ select 'null.type()'::jsonpath;
 (1 row)
 
 select '1.type()'::jsonpath;
- jsonpath 
-----------
- 1.type()
-(1 row)
-
+ERROR:  trailing junk after numeric literal at or near "1.t" of jsonpath input
+LINE 1: select '1.type()'::jsonpath;
+               ^
 select '(1).type()'::jsonpath;
- jsonpath 
-----------
1.type()
+  jsonpath  
+------------
(1).type()
 (1 row)
 
 select '1.2.type()'::jsonpath;
-  jsonpath  
-------------
1.2.type()
+   jsonpath   
+--------------
(1.2).type()
 (1 row)
 
 select '"aaa".type()'::jsonpath;
@@ -545,9 +543,9 @@ select '(($))'::jsonpath;
 (1 row)
 
 select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
-                    jsonpath                     
--------------------------------------------------
- (($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
+                     jsonpath                      
+---------------------------------------------------
+ (($ + 1)."a" + (2)."b"?(@ > 1 || exists (@."c")))
 (1 row)
 
 select '$ ? (@.a < 1)'::jsonpath;
@@ -569,17 +567,23 @@ select '$ ? (@.a < +1)'::jsonpath;
 (1 row)
 
 select '$ ? (@.a < .1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < .1)'::jsonpath;
-               ^
+    jsonpath     
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
 select '$ ? (@.a < -.1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < -.1)'::jsonpath;
-               ^
+     jsonpath     
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
 select '$ ? (@.a < +.1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < +.1)'::jsonpath;
-               ^
+    jsonpath     
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
 select '$ ? (@.a < 0.1)'::jsonpath;
     jsonpath     
 -----------------
@@ -635,17 +639,23 @@ select '$ ? (@.a < +1e1)'::jsonpath;
 (1 row)
 
 select '$ ? (@.a < .1e1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < .1e1)'::jsonpath;
-               ^
+   jsonpath    
+---------------
+ $?(@."a" < 1)
+(1 row)
+
 select '$ ? (@.a < -.1e1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < -.1e1)'::jsonpath;
-               ^
+    jsonpath    
+----------------
+ $?(@."a" < -1)
+(1 row)
+
 select '$ ? (@.a < +.1e1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < +.1e1)'::jsonpath;
-               ^
+   jsonpath    
+---------------
+ $?(@."a" < 1)
+(1 row)
+
 select '$ ? (@.a < 0.1e1)'::jsonpath;
    jsonpath    
 ---------------
@@ -701,17 +711,23 @@ select '$ ? (@.a < +1e-1)'::jsonpath;
 (1 row)
 
 select '$ ? (@.a < .1e-1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < .1e-1)'::jsonpath;
-               ^
+     jsonpath     
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
 select '$ ? (@.a < -.1e-1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < -.1e-1)'::jsonpath;
-               ^
+     jsonpath      
+-------------------
+ $?(@."a" < -0.01)
+(1 row)
+
 select '$ ? (@.a < +.1e-1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < +.1e-1)'::jsonpath;
-               ^
+     jsonpath     
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
 select '$ ? (@.a < 0.1e-1)'::jsonpath;
      jsonpath     
 ------------------
@@ -767,17 +783,23 @@ select '$ ? (@.a < +1e+1)'::jsonpath;
 (1 row)
 
 select '$ ? (@.a < .1e+1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < .1e+1)'::jsonpath;
-               ^
+   jsonpath    
+---------------
+ $?(@."a" < 1)
+(1 row)
+
 select '$ ? (@.a < -.1e+1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < -.1e+1)'::jsonpath;
-               ^
+    jsonpath    
+----------------
+ $?(@."a" < -1)
+(1 row)
+
 select '$ ? (@.a < +.1e+1)'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '$ ? (@.a < +.1e+1)'::jsonpath;
-               ^
+   jsonpath    
+---------------
+ $?(@."a" < 1)
+(1 row)
+
 select '$ ? (@.a < 0.1e+1)'::jsonpath;
    jsonpath    
 ---------------
@@ -821,7 +843,7 @@ select '0'::jsonpath;
 (1 row)
 
 select '00'::jsonpath;
-ERROR:  syntax error, unexpected IDENT_P at end of jsonpath input
+ERROR:  trailing junk after numeric literal at or near "00" of jsonpath input
 LINE 1: select '00'::jsonpath;
                ^
 select '0.0'::jsonpath;
@@ -878,30 +900,60 @@ select '0.0010e+2'::jsonpath;
  0.10
 (1 row)
 
-select '1e'::jsonpath;
-ERROR:  invalid floating point number at or near "1e" of jsonpath input
-LINE 1: select '1e'::jsonpath;
-               ^
-select '1.e'::jsonpath;
+select '.001'::jsonpath;
+ jsonpath 
+----------
+ 0.001
+(1 row)
+
+select '.001e1'::jsonpath;
+ jsonpath 
+----------
+ 0.01
+(1 row)
+
+select '1.'::jsonpath;
+ jsonpath 
+----------
+ 1
+(1 row)
+
+select '1.e1'::jsonpath;
  jsonpath 
 ----------
- 1."e"
+ 10
 (1 row)
 
+select '1a'::jsonpath;
+ERROR:  trailing junk after numeric literal at or near "1a" of jsonpath input
+LINE 1: select '1a'::jsonpath;
+               ^
+select '1e'::jsonpath;
+ERROR:  trailing junk after numeric literal at or near "1e" of jsonpath input
+LINE 1: select '1e'::jsonpath;
+               ^
+select '1.e'::jsonpath;
+ERROR:  trailing junk after numeric literal at or near "1.e" of jsonpath input
+LINE 1: select '1.e'::jsonpath;
+               ^
+select '1.2a'::jsonpath;
+ERROR:  trailing junk after numeric literal at or near "1.2a" of jsonpath input
+LINE 1: select '1.2a'::jsonpath;
+               ^
 select '1.2e'::jsonpath;
-ERROR:  invalid floating point number at or near "1.2e" of jsonpath input
+ERROR:  trailing junk after numeric literal at or near "1.2e" of jsonpath input
 LINE 1: select '1.2e'::jsonpath;
                ^
 select '1.2.e'::jsonpath;
- jsonpath 
-----------
1.2."e"
+ jsonpath  
+-----------
(1.2)."e"
 (1 row)
 
 select '(1.2).e'::jsonpath;
- jsonpath 
-----------
1.2."e"
+ jsonpath  
+-----------
(1.2)."e"
 (1 row)
 
 select '1e3'::jsonpath;
@@ -913,19 +965,19 @@ select '1e3'::jsonpath;
 select '1.e3'::jsonpath;
  jsonpath 
 ----------
- 1."e3"
+ 1000
 (1 row)
 
 select '1.e3.e'::jsonpath;
   jsonpath  
 ------------
1."e3"."e"
(1000)."e"
 (1 row)
 
 select '1.e3.e4'::jsonpath;
   jsonpath   
 -------------
1."e3"."e4"
(1000)."e4"
 (1 row)
 
 select '1.2e3'::jsonpath;
@@ -934,31 +986,49 @@ select '1.2e3'::jsonpath;
  1200
 (1 row)
 
+select '1.2e3a'::jsonpath;
+ERROR:  trailing junk after numeric literal at or near "1.2e3a" of jsonpath input
+LINE 1: select '1.2e3a'::jsonpath;
+               ^
 select '1.2.e3'::jsonpath;
- jsonpath 
-----------
1.2."e3"
+  jsonpath  
+------------
(1.2)."e3"
 (1 row)
 
 select '(1.2).e3'::jsonpath;
+  jsonpath  
+------------
+ (1.2)."e3"
+(1 row)
+
+select '1..e'::jsonpath;
  jsonpath 
 ----------
1.2."e3"
(1)."e"
 (1 row)
 
-select '1..e'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '1..e'::jsonpath;
-               ^
 select '1..e3'::jsonpath;
-ERROR:  syntax error, unexpected '.' at or near "." of jsonpath input
-LINE 1: select '1..e3'::jsonpath;
-               ^
+ jsonpath 
+----------
+ (1)."e3"
+(1 row)
+
 select '(1.).e'::jsonpath;
-ERROR:  syntax error, unexpected ')' at or near ")" of jsonpath input
-LINE 1: select '(1.).e'::jsonpath;
-               ^
+ jsonpath 
+----------
+ (1)."e"
+(1 row)
+
 select '(1.).e3'::jsonpath;
-ERROR:  syntax error, unexpected ')' at or near ")" of jsonpath input
-LINE 1: select '(1.).e3'::jsonpath;
-               ^
+ jsonpath 
+----------
+ (1)."e3"
+(1 row)
+
+select '1?(2>3)'::jsonpath;
+  jsonpath   
+-------------
+ (1)?(2 > 3)
+(1 row)
+
index 17ab7757831d0edf73034d404596e1817f5453c7..d491714614acc68790e7346d55a3f07e572db8d4 100644 (file)
@@ -163,8 +163,14 @@ select '0.0010'::jsonpath;
 select '0.0010e-1'::jsonpath;
 select '0.0010e+1'::jsonpath;
 select '0.0010e+2'::jsonpath;
+select '.001'::jsonpath;
+select '.001e1'::jsonpath;
+select '1.'::jsonpath;
+select '1.e1'::jsonpath;
+select '1a'::jsonpath;
 select '1e'::jsonpath;
 select '1.e'::jsonpath;
+select '1.2a'::jsonpath;
 select '1.2e'::jsonpath;
 select '1.2.e'::jsonpath;
 select '(1.2).e'::jsonpath;
@@ -173,9 +179,11 @@ select '1.e3'::jsonpath;
 select '1.e3.e'::jsonpath;
 select '1.e3.e4'::jsonpath;
 select '1.2e3'::jsonpath;
+select '1.2e3a'::jsonpath;
 select '1.2.e3'::jsonpath;
 select '(1.2).e3'::jsonpath;
 select '1..e'::jsonpath;
 select '1..e3'::jsonpath;
 select '(1.).e'::jsonpath;
 select '(1.).e3'::jsonpath;
+select '1?(2>3)'::jsonpath;