From: Andrew Dunstan Date: Mon, 1 Dec 2014 16:28:45 +0000 (-0500) Subject: Fix hstore_to_json_loose's detection of valid JSON number values. X-Git-Tag: REL9_4_0~27 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f82132f4a15e2a02d984b3ba0ef5b10da1f489bd;p=postgresql 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. --- diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 6ce3047215..cd01b2fadd 100644 --- a/contrib/hstore/hstore_io.c +++ b/contrib/hstore/hstore_io.c @@ -12,6 +12,7 @@ #include "libpq/pqformat.h" #include "utils/builtins.h" #include "utils/json.h" +#include "utils/jsonapi.h" #include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -1240,7 +1241,6 @@ hstore_to_json_loose(PG_FUNCTION_ARGS) int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); - bool is_number; StringInfoData tmp, dst; @@ -1267,48 +1267,9 @@ hstore_to_json_loose(PG_FUNCTION_ARGS) appendStringInfoString(&dst, "false"); else { - is_number = false; resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); - - /* - * don't treat something with a leading zero followed by another - * digit as numeric - could be a zip code or similar - */ - if (tmp.len > 0 && - !(tmp.data[0] == '0' && - isdigit((unsigned char) tmp.data[1])) && - strspn(tmp.data, "+-0123456789Ee.") == tmp.len) - { - /* - * might be a number. See if we can input it as a numeric - * value. Ignore any actual parsed value. - */ - char *endptr = "junk"; - long lval; - - lval = strtol(tmp.data, &endptr, 10); - (void) lval; - if (*endptr == '\0') - { - /* - * strol man page says this means the whole string is - * valid - */ - is_number = true; - } - else - { - /* not an int - try a double */ - double dval; - - dval = strtod(tmp.data, &endptr); - (void) dval; - if (*endptr == '\0') - is_number = true; - } - } - if (is_number) + if (IsValidJsonNumber(tmp.data, tmp.len)) appendBinaryStringInfo(&dst, tmp.data, tmp.len); else escape_json(&dst, tmp.data); diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index d2bf640e54..a576843234 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -173,6 +173,36 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) (c) == '_' || \ IS_HIGHBIT_SET(c)) +/* utility function to check if a string is a valid JSON number */ +extern bool +IsValidJsonNumber(const char * str, int len) +{ + bool numeric_error; + JsonLexContext dummy_lex; + + + /* + * json_lex_number expects a leading '-' to have been eaten already. + * + * having to cast away the constness of str is ugly, but there's not much + * easy alternative. + */ + if (*str == '-') + { + dummy_lex.input = (char *) str + 1; + dummy_lex.input_length = len - 1; + } + else + { + dummy_lex.input = (char *) str; + dummy_lex.input_length = len; + } + + json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error); + + return ! numeric_error; +} + /* * Input. */ @@ -1338,8 +1368,6 @@ datum_to_json(Datum val, bool is_null, StringInfo result, { char *outputstr; text *jsontext; - bool numeric_error; - JsonLexContext dummy_lex; /* callers are expected to ensure that null keys are not passed in */ Assert( ! (key_scalar && is_null)); @@ -1376,25 +1404,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result, break; case JSONTYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); - if (key_scalar) - { - /* always quote keys */ - escape_json(result, outputstr); - } + /* + * Don't call escape_json for a non-key if it's a valid JSON + * number. + */ + if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr))) + appendStringInfoString(result, outputstr); else - { - /* - * Don't call escape_json for a non-key if it's a valid JSON - * number. - */ - dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr; - dummy_lex.input_length = strlen(dummy_lex.input); - json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error); - if (!numeric_error) - appendStringInfoString(result, outputstr); - else - escape_json(result, outputstr); - } + escape_json(result, outputstr); pfree(outputstr); break; case JSONTYPE_DATE: diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index df057121a1..4ae059bec6 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -117,4 +117,11 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json, int len, bool need_escapes); +/* + * Utility function to check if a string is a valid JSON number. + * + * str agrument does not need to be nul-terminated. + */ +extern bool IsValidJsonNumber(const char * str, int len); + #endif /* JSONAPI_H */