]> granicus.if.org Git - postgresql/commitdiff
Fix hstore_to_json_loose's detection of valid JSON number values.
authorAndrew Dunstan <andrew@dunslane.net>
Mon, 1 Dec 2014 16:28:45 +0000 (11:28 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Mon, 1 Dec 2014 16:40:30 +0000 (11:40 -0500)
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.

contrib/hstore/hstore_io.c
src/backend/utils/adt/json.c
src/include/utils/jsonapi.h

index 6ce3047215ddff917d6ff0d5c61e50c89b74d441..cd01b2faddb11cb5252e7684f969d4ea69222d41 100644 (file)
@@ -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);
index d2bf640e54e72541591434d0ffa8a29411d32948..a576843234ebc1678eee7a223cba1fbac4a07291 100644 (file)
@@ -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:
index df057121a1178e3687580c8def057a65ada63cb6..4ae059bec61afc29e66f02356e75f91c83b36582 100644 (file)
@@ -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 */