Skip to content

Commit 55c8807

Browse files
committed
Fix hstore_to_json_loose's detection of valid JSON number values.
We expose a function IsValidJsonNumber that internally calls the lexer for json numbers. That allows us to use the same test everywhere, instead of inventing a broken test for hstore conversions. The new function is also used in datum_to_json, replacing the code that is now moved to the new function. Backpatch to 9.3 where hstore_to_json_loose was introduced.
1 parent 5c9a4a8 commit 55c8807

File tree

3 files changed

+42
-48
lines changed

3 files changed

+42
-48
lines changed

contrib/hstore/hstore_io.c

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "libpq/pqformat.h"
1313
#include "utils/builtins.h"
1414
#include "utils/json.h"
15+
#include "utils/jsonapi.h"
1516
#include "utils/lsyscache.h"
1617
#include "utils/memutils.h"
1718
#include "utils/typcache.h"
@@ -1253,7 +1254,6 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
12531254
int count = HS_COUNT(in);
12541255
char *base = STRPTR(in);
12551256
HEntry *entries = ARRPTR(in);
1256-
bool is_number;
12571257
StringInfoData tmp,
12581258
dst;
12591259

@@ -1280,48 +1280,9 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
12801280
appendStringInfoString(&dst, "false");
12811281
else
12821282
{
1283-
is_number = false;
12841283
resetStringInfo(&tmp);
12851284
appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
1286-
1287-
/*
1288-
* don't treat something with a leading zero followed by another
1289-
* digit as numeric - could be a zip code or similar
1290-
*/
1291-
if (tmp.len > 0 &&
1292-
!(tmp.data[0] == '0' &&
1293-
isdigit((unsigned char) tmp.data[1])) &&
1294-
strspn(tmp.data, "+-0123456789Ee.") == tmp.len)
1295-
{
1296-
/*
1297-
* might be a number. See if we can input it as a numeric
1298-
* value. Ignore any actual parsed value.
1299-
*/
1300-
char *endptr = "junk";
1301-
long lval;
1302-
1303-
lval = strtol(tmp.data, &endptr, 10);
1304-
(void) lval;
1305-
if (*endptr == '\0')
1306-
{
1307-
/*
1308-
* strol man page says this means the whole string is
1309-
* valid
1310-
*/
1311-
is_number = true;
1312-
}
1313-
else
1314-
{
1315-
/* not an int - try a double */
1316-
double dval;
1317-
1318-
dval = strtod(tmp.data, &endptr);
1319-
(void) dval;
1320-
if (*endptr == '\0')
1321-
is_number = true;
1322-
}
1323-
}
1324-
if (is_number)
1285+
if (IsValidJsonNumber(tmp.data, tmp.len))
13251286
appendBinaryStringInfo(&dst, tmp.data, tmp.len);
13261287
else
13271288
escape_json(&dst, tmp.data);

src/backend/utils/adt/json.c

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,36 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
164164
(c) == '_' || \
165165
IS_HIGHBIT_SET(c))
166166

167+
/* utility function to check if a string is a valid JSON number */
168+
extern bool
169+
IsValidJsonNumber(const char * str, int len)
170+
{
171+
bool numeric_error;
172+
JsonLexContext dummy_lex;
173+
174+
175+
/*
176+
* json_lex_number expects a leading '-' to have been eaten already.
177+
*
178+
* having to cast away the constness of str is ugly, but there's not much
179+
* easy alternative.
180+
*/
181+
if (*str == '-')
182+
{
183+
dummy_lex.input = (char *) str + 1;
184+
dummy_lex.input_length = len - 1;
185+
}
186+
else
187+
{
188+
dummy_lex.input = (char *) str;
189+
dummy_lex.input_length = len;
190+
}
191+
192+
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
193+
194+
return ! numeric_error;
195+
}
196+
167197
/*
168198
* Input.
169199
*/
@@ -1306,8 +1336,6 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
13061336
{
13071337
char *outputstr;
13081338
text *jsontext;
1309-
bool numeric_error;
1310-
JsonLexContext dummy_lex;
13111339

13121340
if (is_null)
13131341
{
@@ -1332,12 +1360,10 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
13321360
case JSONTYPE_NUMERIC:
13331361
outputstr = OidOutputFunctionCall(outfuncoid, val);
13341362
/*
1335-
* Don't call escape_json here if it's a valid JSON number.
1363+
* Don't call escape_json for a non-key if it's a valid JSON
1364+
* number.
13361365
*/
1337-
dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr;
1338-
dummy_lex.input_length = strlen(dummy_lex.input);
1339-
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
1340-
if (! numeric_error)
1366+
if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr)))
13411367
appendStringInfoString(result, outputstr);
13421368
else
13431369
escape_json(result, outputstr);

src/include/utils/jsonapi.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,11 @@ extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem);
107107
*/
108108
extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
109109

110+
/*
111+
* Utility function to check if a string is a valid JSON number.
112+
*
113+
* str agrument does not need to be nul-terminated.
114+
*/
115+
extern bool IsValidJsonNumber(const char * str, int len);
116+
110117
#endif /* JSONAPI_H */

0 commit comments

Comments
 (0)