]> granicus.if.org Git - postgresql/commitdiff
Get rid of bogus dependency on typcategory in to_json() and friends.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 9 May 2014 16:55:06 +0000 (12:55 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 9 May 2014 16:55:06 +0000 (12:55 -0400)
These functions were relying on typcategory to identify arrays and
composites, which is not reliable and not the normal way to do it.
Using typcategory to identify boolean, numeric types, and json itself is
also pretty questionable, though the code in those cases didn't seem to be
at risk of anything worse than wrong output.  Instead, use the standard
lsyscache functions to identify arrays and composites, and rely on a direct
check of the type OID for the other cases.

In HEAD, also be sure to look through domains so that a domain is treated
the same as its base type for conversions to JSON.  However, this is a
small behavioral change; given the lack of field complaints, we won't
back-patch it.

In passing, refactor so that there's only one copy of the code that decides
which conversion strategy to apply, not multiple copies that could (and
have) gotten out of sync.

src/backend/utils/adt/json.c

index aec50cb50061a8611942b1245dbd95410fd48bd7..d22aa131c54af444a72ba6c9b54cf184bb4016af 100644 (file)
@@ -70,6 +70,17 @@ typedef enum                                 /* required operations on state stack */
        JSON_STACKOP_POP                        /* pop, or expect end of input if no stack */
 } JsonStackOp;
 
+typedef enum                                   /* type categories for datum_to_json */
+{
+       JSONTYPE_NULL,                          /* null, so we didn't bother to identify */
+       JSONTYPE_BOOL,                          /* boolean (built-in types only) */
+       JSONTYPE_NUMERIC,                       /* numeric (ditto) */
+       JSONTYPE_JSON,                          /* JSON itself */
+       JSONTYPE_ARRAY,                         /* array */
+       JSONTYPE_COMPOSITE,                     /* composite */
+       JSONTYPE_OTHER                          /* all else */
+} JsonTypeCategory;
+
 static void json_validate_cstring(char *input);
 static void json_lex(JsonLexContext *lex);
 static void json_lex_string(JsonLexContext *lex);
@@ -82,13 +93,16 @@ static void composite_to_json(Datum composite, StringInfo result,
                                                          bool use_line_feeds);
 static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
                                  Datum *vals, bool *nulls, int *valcount,
-                                 TYPCATEGORY tcategory, Oid typoutputfunc,
+                                 JsonTypeCategory tcategory, Oid outfuncoid,
                                  bool use_line_feeds);
 static void array_to_json_internal(Datum array, StringInfo result,
-                                                                  bool use_line_feeds);
+                                          bool use_line_feeds);
+static void json_categorize_type(Oid typoid,
+                                        JsonTypeCategory *tcategory,
+                                        Oid *outfuncoid);
+static void datum_to_json(Datum val, bool is_null, StringInfo result,
+                         JsonTypeCategory tcategory, Oid outfuncoid);
 
-/* fake type category for JSON so we can distinguish it in datum_to_json */
-#define TYPCATEGORY_JSON 'j'
 /* chars to consider as part of an alphanumeric token */
 #define JSON_ALPHANUMERIC_CHAR(c)  \
        (((c) >= 'a' && (c) <= 'z') || \
@@ -816,14 +830,67 @@ extract_mb_char(char *s)
 }
 
 /*
- * Turn a scalar Datum into JSON, appending the string to "result".
+ * Determine how we want to print values of a given type in datum_to_json.
+ *
+ * Given the datatype OID, return its JsonTypeCategory, as well as the type's
+ * output function OID.  If the returned category is JSONTYPE_CAST, we
+ * return the OID of the type->JSON cast function instead.
+ */
+static void
+json_categorize_type(Oid typoid,
+                                        JsonTypeCategory *tcategory,
+                                        Oid *outfuncoid)
+{
+       bool            typisvarlena;
+
+       /*
+        * We should look through domains here, but we'll wait till 9.4.
+        */
+
+       /* We'll usually need to return the type output function */
+       getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
+
+       /* Check for known types */
+       switch (typoid)
+       {
+               case BOOLOID:
+                       *tcategory = JSONTYPE_BOOL;
+                       break;
+
+               case INT2OID:
+               case INT4OID:
+               case INT8OID:
+               case FLOAT4OID:
+               case FLOAT8OID:
+               case NUMERICOID:
+                       *tcategory = JSONTYPE_NUMERIC;
+                       break;
+
+               case JSONOID:
+                       *tcategory = JSONTYPE_JSON;
+                       break;
+
+               default:
+                       /* Check for arrays and composites */
+                       if (OidIsValid(get_element_type(typoid)))
+                               *tcategory = JSONTYPE_ARRAY;
+                       else if (type_is_rowtype(typoid))
+                               *tcategory = JSONTYPE_COMPOSITE;
+                       else
+                               *tcategory = JSONTYPE_OTHER;
+                       break;
+       }
+}
+
+/*
+ * Turn a Datum into JSON text, appending the string to "result".
  *
- * Hand off a non-scalar datum to composite_to_json or array_to_json_internal
- * as appropriate.
+ * tcategory and outfuncoid are from a previous call to json_categorize_type,
+ * except that if is_null is true then they can be invalid.
  */
 static void
 datum_to_json(Datum val, bool is_null, StringInfo result,
-                         TYPCATEGORY tcategory, Oid typoutputfunc)
+                         JsonTypeCategory tcategory, Oid outfuncoid)
 {
        char       *outputstr;
        bool            numeric_error;
@@ -837,20 +904,20 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
        switch (tcategory)
        {
-               case TYPCATEGORY_ARRAY:
+               case JSONTYPE_ARRAY:
                        array_to_json_internal(val, result, false);
                        break;
-               case TYPCATEGORY_COMPOSITE:
+               case JSONTYPE_COMPOSITE:
                        composite_to_json(val, result, false);
                        break;
-               case TYPCATEGORY_BOOLEAN:
+               case JSONTYPE_BOOL:
                        if (DatumGetBool(val))
                                appendStringInfoString(result, "true");
                        else
                                appendStringInfoString(result, "false");
                        break;
-               case TYPCATEGORY_NUMERIC:
-                       outputstr = OidOutputFunctionCall(typoutputfunc, val);
+               case JSONTYPE_NUMERIC:
+                       outputstr = OidOutputFunctionCall(outfuncoid, val);
 
                        /*
                         * Don't call escape_json here if it's a valid JSON number.
@@ -863,14 +930,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                                escape_json(result, outputstr);
                        pfree(outputstr);
                        break;
-               case TYPCATEGORY_JSON:
+               case JSONTYPE_JSON:
                        /* JSON will already be escaped */
-                       outputstr = OidOutputFunctionCall(typoutputfunc, val);
+                       outputstr = OidOutputFunctionCall(outfuncoid, val);
                        appendStringInfoString(result, outputstr);
                        pfree(outputstr);
                        break;
                default:
-                       outputstr = OidOutputFunctionCall(typoutputfunc, val);
+                       outputstr = OidOutputFunctionCall(outfuncoid, val);
                        escape_json(result, outputstr);
                        pfree(outputstr);
                        break;
@@ -884,8 +951,8 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
  */
 static void
 array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
-                                 bool *nulls, int *valcount, TYPCATEGORY tcategory,
-                                 Oid typoutputfunc, bool use_line_feeds)
+                                 bool *nulls, int *valcount, JsonTypeCategory tcategory,
+                                 Oid outfuncoid, bool use_line_feeds)
 {
        int                     i;
        const char *sep;
@@ -904,7 +971,7 @@ array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
                if (dim + 1 == ndims)
                {
                        datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory,
-                                                 typoutputfunc);
+                                                 outfuncoid);
                        (*valcount)++;
                }
                else
@@ -914,7 +981,7 @@ array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
                         * we'll say no.
                         */
                        array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
-                                                         valcount, tcategory, typoutputfunc, false);
+                                                         valcount, tcategory, outfuncoid, false);
                }
        }
 
@@ -937,11 +1004,9 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
        bool       *nulls;
        int16           typlen;
        bool            typbyval;
-       char            typalign,
-                               typdelim;
-       Oid                     typioparam;
-       Oid                     typoutputfunc;
-       TYPCATEGORY tcategory;
+       char            typalign;
+       JsonTypeCategory tcategory;
+       Oid                     outfuncoid;
 
        ndim = ARR_NDIM(v);
        dim = ARR_DIMS(v);
@@ -953,23 +1018,18 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
                return;
        }
 
-       get_type_io_data(element_type, IOFunc_output,
-                                        &typlen, &typbyval, &typalign,
-                                        &typdelim, &typioparam, &typoutputfunc);
+       get_typlenbyvalalign(element_type,
+                                                &typlen, &typbyval, &typalign);
+
+       json_categorize_type(element_type,
+                                                &tcategory, &outfuncoid);
 
        deconstruct_array(v, element_type, typlen, typbyval,
                                          typalign, &elements, &nulls,
                                          &nitems);
 
-       if (element_type == RECORDOID)
-               tcategory = TYPCATEGORY_COMPOSITE;
-       else if (element_type == JSONOID)
-               tcategory = TYPCATEGORY_JSON;
-       else
-               tcategory = TypeCategory(element_type);
-
        array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory,
-                                         typoutputfunc, use_line_feeds);
+                                         outfuncoid, use_line_feeds);
 
        pfree(elements);
        pfree(nulls);
@@ -1009,13 +1069,11 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
 
        for (i = 0; i < tupdesc->natts; i++)
        {
-               Datum           val,
-                                       origval;
+               Datum           val;
                bool            isnull;
                char       *attname;
-               TYPCATEGORY tcategory;
-               Oid                     typoutput;
-               bool            typisvarlena;
+               JsonTypeCategory tcategory;
+               Oid                     outfuncoid;
 
                if (tupdesc->attrs[i]->attisdropped)
                        continue;
@@ -1028,34 +1086,18 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
                escape_json(result, attname);
                appendStringInfoChar(result, ':');
 
-               origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
-
-               if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID)
-                       tcategory = TYPCATEGORY_ARRAY;
-               else if (tupdesc->attrs[i]->atttypid == RECORDOID)
-                       tcategory = TYPCATEGORY_COMPOSITE;
-               else if (tupdesc->attrs[i]->atttypid == JSONOID)
-                       tcategory = TYPCATEGORY_JSON;
-               else
-                       tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
+               val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
 
-               getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
-                                                 &typoutput, &typisvarlena);
-
-               /*
-                * If we have a toasted datum, forcibly detoast it here to avoid
-                * memory leakage inside the type's output routine.
-                */
-               if (typisvarlena && !isnull)
-                       val = PointerGetDatum(PG_DETOAST_DATUM(origval));
+               if (isnull)
+               {
+                       tcategory = JSONTYPE_NULL;
+                       outfuncoid = InvalidOid;
+               }
                else
-                       val = origval;
-
-               datum_to_json(val, isnull, result, tcategory, typoutput);
+                       json_categorize_type(tupdesc->attrs[i]->atttypid,
+                                                                &tcategory, &outfuncoid);
 
-               /* Clean up detoasted copy, if any */
-               if (val != origval)
-                       pfree(DatumGetPointer(val));
+               datum_to_json(val, isnull, result, tcategory, outfuncoid);
        }
 
        appendStringInfoChar(result, '}');