Skip to content

Commit e09996f

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 4e86f1b commit e09996f

File tree

3 files changed

+46
-61
lines changed

3 files changed

+46
-61
lines changed

contrib/hstore/hstore_io.c

+2-41
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/jsonb.h"
1617
#include "utils/lsyscache.h"
1718
#include "utils/memutils.h"
@@ -1240,7 +1241,6 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
12401241
int count = HS_COUNT(in);
12411242
char *base = STRPTR(in);
12421243
HEntry *entries = ARRPTR(in);
1243-
bool is_number;
12441244
StringInfoData tmp,
12451245
dst;
12461246

@@ -1267,48 +1267,9 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
12671267
appendStringInfoString(&dst, "false");
12681268
else
12691269
{
1270-
is_number = false;
12711270
resetStringInfo(&tmp);
12721271
appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
1273-
1274-
/*
1275-
* don't treat something with a leading zero followed by another
1276-
* digit as numeric - could be a zip code or similar
1277-
*/
1278-
if (tmp.len > 0 &&
1279-
!(tmp.data[0] == '0' &&
1280-
isdigit((unsigned char) tmp.data[1])) &&
1281-
strspn(tmp.data, "+-0123456789Ee.") == tmp.len)
1282-
{
1283-
/*
1284-
* might be a number. See if we can input it as a numeric
1285-
* value. Ignore any actual parsed value.
1286-
*/
1287-
char *endptr = "junk";
1288-
long lval;
1289-
1290-
lval = strtol(tmp.data, &endptr, 10);
1291-
(void) lval;
1292-
if (*endptr == '\0')
1293-
{
1294-
/*
1295-
* strol man page says this means the whole string is
1296-
* valid
1297-
*/
1298-
is_number = true;
1299-
}
1300-
else
1301-
{
1302-
/* not an int - try a double */
1303-
double dval;
1304-
1305-
dval = strtod(tmp.data, &endptr);
1306-
(void) dval;
1307-
if (*endptr == '\0')
1308-
is_number = true;
1309-
}
1310-
}
1311-
if (is_number)
1272+
if (IsValidJsonNumber(tmp.data, tmp.len))
13121273
appendBinaryStringInfo(&dst, tmp.data, tmp.len);
13131274
else
13141275
escape_json(&dst, tmp.data);

src/backend/utils/adt/json.c

+37-20
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,36 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
173173
(c) == '_' || \
174174
IS_HIGHBIT_SET(c))
175175

176+
/* utility function to check if a string is a valid JSON number */
177+
extern bool
178+
IsValidJsonNumber(const char * str, int len)
179+
{
180+
bool numeric_error;
181+
JsonLexContext dummy_lex;
182+
183+
184+
/*
185+
* json_lex_number expects a leading '-' to have been eaten already.
186+
*
187+
* having to cast away the constness of str is ugly, but there's not much
188+
* easy alternative.
189+
*/
190+
if (*str == '-')
191+
{
192+
dummy_lex.input = (char *) str + 1;
193+
dummy_lex.input_length = len - 1;
194+
}
195+
else
196+
{
197+
dummy_lex.input = (char *) str;
198+
dummy_lex.input_length = len;
199+
}
200+
201+
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
202+
203+
return ! numeric_error;
204+
}
205+
176206
/*
177207
* Input.
178208
*/
@@ -1338,8 +1368,6 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
13381368
{
13391369
char *outputstr;
13401370
text *jsontext;
1341-
bool numeric_error;
1342-
JsonLexContext dummy_lex;
13431371

13441372
/* callers are expected to ensure that null keys are not passed in */
13451373
Assert( ! (key_scalar && is_null));
@@ -1376,25 +1404,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
13761404
break;
13771405
case JSONTYPE_NUMERIC:
13781406
outputstr = OidOutputFunctionCall(outfuncoid, val);
1379-
if (key_scalar)
1380-
{
1381-
/* always quote keys */
1382-
escape_json(result, outputstr);
1383-
}
1407+
/*
1408+
* Don't call escape_json for a non-key if it's a valid JSON
1409+
* number.
1410+
*/
1411+
if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr)))
1412+
appendStringInfoString(result, outputstr);
13841413
else
1385-
{
1386-
/*
1387-
* Don't call escape_json for a non-key if it's a valid JSON
1388-
* number.
1389-
*/
1390-
dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr;
1391-
dummy_lex.input_length = strlen(dummy_lex.input);
1392-
json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
1393-
if (!numeric_error)
1394-
appendStringInfoString(result, outputstr);
1395-
else
1396-
escape_json(result, outputstr);
1397-
}
1414+
escape_json(result, outputstr);
13981415
pfree(outputstr);
13991416
break;
14001417
case JSONTYPE_DATE:

src/include/utils/jsonapi.h

+7
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,11 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
117117
int len,
118118
bool need_escapes);
119119

120+
/*
121+
* Utility function to check if a string is a valid JSON number.
122+
*
123+
* str agrument does not need to be nul-terminated.
124+
*/
125+
extern bool IsValidJsonNumber(const char * str, int len);
126+
120127
#endif /* JSONAPI_H */

0 commit comments

Comments
 (0)