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

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/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

Lines changed: 37 additions & 20 deletions
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

Lines changed: 7 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy