Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 102a5c1

Browse files
committed
SQL JSON path enhanced numeric literals
Add support for non-decimal integer literals and underscores in numeric literals to SQL JSON path language. This follows the rules of ECMAScript, as referred to by the SQL standard. Internally, all the numeric literal parsing of jsonpath goes through numeric_in, which already supports all this, so this patch is just a bit of lexer work and some tests and documentation. Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/b11b25bb-6ec1-d42f-cedd-311eae59e1fb@enterprisedb.com
1 parent 6949b92 commit 102a5c1

File tree

5 files changed

+271
-14
lines changed

5 files changed

+271
-14
lines changed

doc/src/sgml/json.sgml

+12
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,18 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
779779
</listitem>
780780
</itemizedlist>
781781

782+
<para>
783+
Numeric literals in SQL/JSON path expressions follow JavaScript rules,
784+
which are different from both SQL and JSON in some minor details. For
785+
example, SQL/JSON path allows <literal>.1</literal> and
786+
<literal>1.</literal>, which are invalid in JSON. Non-decimal integer
787+
literals and underscore separators are supported, for example,
788+
<literal>1_000_000</literal>, <literal>0x1EEE_FFFF</literal>,
789+
<literal>0o273</literal>, <literal>0b100101</literal>. In SQL/JSON path
790+
(and in JavaScript, but not in SQL proper), there must not be an underscore
791+
separator directly after the radix prefix.
792+
</para>
793+
782794
<para>
783795
An SQL/JSON path expression is typically written in an SQL query as an
784796
SQL character string literal, so it must be enclosed in single quotes,

src/backend/catalog/sql_features.txt

+1
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ T836 SQL/JSON path language: starts with predicate YES
553553
T837 SQL/JSON path language: regex_like predicate YES
554554
T838 JSON_TABLE: PLAN DEFAULT clause NO
555555
T839 Formatted cast of datetimes to/from character strings NO
556+
T840 Hex integer literals in SQL/JSON path language YES SQL:202x draft
556557
M001 Datalinks NO
557558
M002 Datalinks via SQL/CLI NO
558559
M003 Datalinks via Embedded SQL NO

src/backend/utils/adt/jsonpath_scan.l

+46-14
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,32 @@ blank [ \t\n\r\f]
9090
/* "other" means anything that's not special, blank, or '\' or '"' */
9191
other [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
9292

93-
digit [0-9]
94-
integer (0|[1-9]{digit}*)
95-
decimal ({integer}\.{digit}*|\.{digit}+)
96-
real ({integer}|{decimal})[Ee][-+]?{digit}+
97-
realfail ({integer}|{decimal})[Ee][-+]
98-
99-
integer_junk {integer}{other}
93+
decdigit [0-9]
94+
hexdigit [0-9A-Fa-f]
95+
octdigit [0-7]
96+
bindigit [0-1]
97+
98+
/* DecimalInteger in ECMAScript; must not start with 0 unless it's exactly 0 */
99+
decinteger (0|[1-9](_?{decdigit})*)
100+
/* DecimalDigits in ECMAScript; only used as part of other rules */
101+
decdigits {decdigit}(_?{decdigit})*
102+
/* Non-decimal integers; in ECMAScript, these must not have underscore after prefix */
103+
hexinteger 0[xX]{hexdigit}(_?{hexdigit})*
104+
octinteger 0[oO]{octdigit}(_?{octdigit})*
105+
bininteger 0[bB]{bindigit}(_?{bindigit})*
106+
107+
decimal ({decinteger}\.{decdigits}?|\.{decdigits})
108+
real ({decinteger}|{decimal})[Ee][-+]?{decdigits}
109+
realfail ({decinteger}|{decimal})[Ee][-+]
110+
111+
decinteger_junk {decinteger}{other}
100112
decimal_junk {decimal}{other}
101113
real_junk {real}{other}
102114

103-
hex_dig [0-9A-Fa-f]
104-
unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
105-
unicodefail \\u({hex_dig}{0,3}|\{{hex_dig}{0,6})
106-
hex_char \\x{hex_dig}{2}
107-
hex_fail \\x{hex_dig}{0,1}
115+
unicode \\u({hexdigit}{4}|\{{hexdigit}{1,6}\})
116+
unicodefail \\u({hexdigit}{0,3}|\{{hexdigit}{0,6})
117+
hex_char \\x{hexdigit}{2}
118+
hex_fail \\x{hexdigit}{0,1}
108119

109120
%%
110121

@@ -274,7 +285,28 @@ hex_fail \\x{hex_dig}{0,1}
274285
return NUMERIC_P;
275286
}
276287

277-
{integer} {
288+
{decinteger} {
289+
addstring(true, yytext, yyleng);
290+
addchar(false, '\0');
291+
yylval->str = scanstring;
292+
return INT_P;
293+
}
294+
295+
{hexinteger} {
296+
addstring(true, yytext, yyleng);
297+
addchar(false, '\0');
298+
yylval->str = scanstring;
299+
return INT_P;
300+
}
301+
302+
{octinteger} {
303+
addstring(true, yytext, yyleng);
304+
addchar(false, '\0');
305+
yylval->str = scanstring;
306+
return INT_P;
307+
}
308+
309+
{bininteger} {
278310
addstring(true, yytext, yyleng);
279311
addchar(false, '\0');
280312
yylval->str = scanstring;
@@ -287,7 +319,7 @@ hex_fail \\x{hex_dig}{0,1}
287319
"invalid numeric literal");
288320
yyterminate();
289321
}
290-
{integer_junk} {
322+
{decinteger_junk} {
291323
jsonpath_yyerror(
292324
NULL, escontext,
293325
"trailing junk after numeric literal");

src/test/regress/expected/jsonpath.out

+162
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ select '$ ? (@.a < +10.1e+1)'::jsonpath;
836836
$?(@."a" < 101)
837837
(1 row)
838838

839+
-- numeric literals
839840
select '0'::jsonpath;
840841
jsonpath
841842
----------
@@ -846,6 +847,10 @@ select '00'::jsonpath;
846847
ERROR: trailing junk after numeric literal at or near "00" of jsonpath input
847848
LINE 1: select '00'::jsonpath;
848849
^
850+
select '0755'::jsonpath;
851+
ERROR: syntax error at end of jsonpath input
852+
LINE 1: select '0755'::jsonpath;
853+
^
849854
select '0.0'::jsonpath;
850855
jsonpath
851856
----------
@@ -1032,6 +1037,163 @@ select '1?(2>3)'::jsonpath;
10321037
(1)?(2 > 3)
10331038
(1 row)
10341039

1040+
-- nondecimal
1041+
select '0b100101'::jsonpath;
1042+
jsonpath
1043+
----------
1044+
37
1045+
(1 row)
1046+
1047+
select '0o273'::jsonpath;
1048+
jsonpath
1049+
----------
1050+
187
1051+
(1 row)
1052+
1053+
select '0x42F'::jsonpath;
1054+
jsonpath
1055+
----------
1056+
1071
1057+
(1 row)
1058+
1059+
-- error cases
1060+
select '0b'::jsonpath;
1061+
ERROR: trailing junk after numeric literal at or near "0b" of jsonpath input
1062+
LINE 1: select '0b'::jsonpath;
1063+
^
1064+
select '1b'::jsonpath;
1065+
ERROR: trailing junk after numeric literal at or near "1b" of jsonpath input
1066+
LINE 1: select '1b'::jsonpath;
1067+
^
1068+
select '0b0x'::jsonpath;
1069+
ERROR: syntax error at end of jsonpath input
1070+
LINE 1: select '0b0x'::jsonpath;
1071+
^
1072+
select '0o'::jsonpath;
1073+
ERROR: trailing junk after numeric literal at or near "0o" of jsonpath input
1074+
LINE 1: select '0o'::jsonpath;
1075+
^
1076+
select '1o'::jsonpath;
1077+
ERROR: trailing junk after numeric literal at or near "1o" of jsonpath input
1078+
LINE 1: select '1o'::jsonpath;
1079+
^
1080+
select '0o0x'::jsonpath;
1081+
ERROR: syntax error at end of jsonpath input
1082+
LINE 1: select '0o0x'::jsonpath;
1083+
^
1084+
select '0x'::jsonpath;
1085+
ERROR: trailing junk after numeric literal at or near "0x" of jsonpath input
1086+
LINE 1: select '0x'::jsonpath;
1087+
^
1088+
select '1x'::jsonpath;
1089+
ERROR: trailing junk after numeric literal at or near "1x" of jsonpath input
1090+
LINE 1: select '1x'::jsonpath;
1091+
^
1092+
select '0x0y'::jsonpath;
1093+
ERROR: syntax error at end of jsonpath input
1094+
LINE 1: select '0x0y'::jsonpath;
1095+
^
1096+
-- underscores
1097+
select '1_000_000'::jsonpath;
1098+
jsonpath
1099+
----------
1100+
1000000
1101+
(1 row)
1102+
1103+
select '1_2_3'::jsonpath;
1104+
jsonpath
1105+
----------
1106+
123
1107+
(1 row)
1108+
1109+
select '0x1EEE_FFFF'::jsonpath;
1110+
jsonpath
1111+
-----------
1112+
518979583
1113+
(1 row)
1114+
1115+
select '0o2_73'::jsonpath;
1116+
jsonpath
1117+
----------
1118+
187
1119+
(1 row)
1120+
1121+
select '0b10_0101'::jsonpath;
1122+
jsonpath
1123+
----------
1124+
37
1125+
(1 row)
1126+
1127+
select '1_000.000_005'::jsonpath;
1128+
jsonpath
1129+
-------------
1130+
1000.000005
1131+
(1 row)
1132+
1133+
select '1_000.'::jsonpath;
1134+
jsonpath
1135+
----------
1136+
1000
1137+
(1 row)
1138+
1139+
select '.000_005'::jsonpath;
1140+
jsonpath
1141+
----------
1142+
0.000005
1143+
(1 row)
1144+
1145+
select '1_000.5e0_1'::jsonpath;
1146+
jsonpath
1147+
----------
1148+
10005
1149+
(1 row)
1150+
1151+
-- error cases
1152+
select '_100'::jsonpath;
1153+
ERROR: syntax error at end of jsonpath input
1154+
LINE 1: select '_100'::jsonpath;
1155+
^
1156+
select '100_'::jsonpath;
1157+
ERROR: trailing junk after numeric literal at or near "100_" of jsonpath input
1158+
LINE 1: select '100_'::jsonpath;
1159+
^
1160+
select '100__000'::jsonpath;
1161+
ERROR: syntax error at end of jsonpath input
1162+
LINE 1: select '100__000'::jsonpath;
1163+
^
1164+
select '_1_000.5'::jsonpath;
1165+
ERROR: syntax error at end of jsonpath input
1166+
LINE 1: select '_1_000.5'::jsonpath;
1167+
^
1168+
select '1_000_.5'::jsonpath;
1169+
ERROR: trailing junk after numeric literal at or near "1_000_" of jsonpath input
1170+
LINE 1: select '1_000_.5'::jsonpath;
1171+
^
1172+
select '1_000._5'::jsonpath;
1173+
ERROR: trailing junk after numeric literal at or near "1_000._" of jsonpath input
1174+
LINE 1: select '1_000._5'::jsonpath;
1175+
^
1176+
select '1_000.5_'::jsonpath;
1177+
ERROR: trailing junk after numeric literal at or near "1_000.5_" of jsonpath input
1178+
LINE 1: select '1_000.5_'::jsonpath;
1179+
^
1180+
select '1_000.5e_1'::jsonpath;
1181+
ERROR: trailing junk after numeric literal at or near "1_000.5e" of jsonpath input
1182+
LINE 1: select '1_000.5e_1'::jsonpath;
1183+
^
1184+
-- underscore after prefix not allowed in JavaScript (but allowed in SQL)
1185+
select '0b_10_0101'::jsonpath;
1186+
ERROR: syntax error at end of jsonpath input
1187+
LINE 1: select '0b_10_0101'::jsonpath;
1188+
^
1189+
select '0o_273'::jsonpath;
1190+
ERROR: syntax error at end of jsonpath input
1191+
LINE 1: select '0o_273'::jsonpath;
1192+
^
1193+
select '0x_42F'::jsonpath;
1194+
ERROR: syntax error at end of jsonpath input
1195+
LINE 1: select '0x_42F'::jsonpath;
1196+
^
10351197
-- test non-error-throwing API
10361198
SELECT str as jsonpath,
10371199
pg_input_is_valid(str,'jsonpath') as ok,

src/test/regress/sql/jsonpath.sql

+50
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,11 @@ select '$ ? (@.a < 10.1e+1)'::jsonpath;
152152
select '$ ? (@.a < -10.1e+1)'::jsonpath;
153153
select '$ ? (@.a < +10.1e+1)'::jsonpath;
154154
155+
-- numeric literals
156+
155157
select '0'::jsonpath;
156158
select '00'::jsonpath;
159+
select '0755'::jsonpath;
157160
select '0.0'::jsonpath;
158161
select '0.000'::jsonpath;
159162
select '0.000e1'::jsonpath;
@@ -188,6 +191,53 @@ select '(1.).e'::jsonpath;
188191
select '(1.).e3'::jsonpath;
189192
select '1?(2>3)'::jsonpath;
190193
194+
-- nondecimal
195+
select '0b100101'::jsonpath;
196+
select '0o273'::jsonpath;
197+
select '0x42F'::jsonpath;
198+
199+
-- error cases
200+
select '0b'::jsonpath;
201+
select '1b'::jsonpath;
202+
select '0b0x'::jsonpath;
203+
204+
select '0o'::jsonpath;
205+
select '1o'::jsonpath;
206+
select '0o0x'::jsonpath;
207+
208+
select '0x'::jsonpath;
209+
select '1x'::jsonpath;
210+
select '0x0y'::jsonpath;
211+
212+
-- underscores
213+
select '1_000_000'::jsonpath;
214+
select '1_2_3'::jsonpath;
215+
select '0x1EEE_FFFF'::jsonpath;
216+
select '0o2_73'::jsonpath;
217+
select '0b10_0101'::jsonpath;
218+
219+
select '1_000.000_005'::jsonpath;
220+
select '1_000.'::jsonpath;
221+
select '.000_005'::jsonpath;
222+
select '1_000.5e0_1'::jsonpath;
223+
224+
-- error cases
225+
select '_100'::jsonpath;
226+
select '100_'::jsonpath;
227+
select '100__000'::jsonpath;
228+
229+
select '_1_000.5'::jsonpath;
230+
select '1_000_.5'::jsonpath;
231+
select '1_000._5'::jsonpath;
232+
select '1_000.5_'::jsonpath;
233+
select '1_000.5e_1'::jsonpath;
234+
235+
-- underscore after prefix not allowed in JavaScript (but allowed in SQL)
236+
select '0b_10_0101'::jsonpath;
237+
select '0o_273'::jsonpath;
238+
select '0x_42F'::jsonpath;
239+
240+
191241
-- test non-error-throwing API
192242
193243
SELECT str as jsonpath,

0 commit comments

Comments
 (0)