Make jsonb casts to scalar types translate JSON null to SQL NULL.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 24 Jan 2025 18:20:44 +0000 (13:20 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 24 Jan 2025 18:20:44 +0000 (13:20 -0500)
Formerly, these cases threw an error "cannot cast jsonb null to type
<whatever>".  That seems less than helpful though.  It's also
inconsistent with the behavior of the ->> operator, which translates
JSON null to SQL NULL, as do some other jsonb functions.

Discussion: https://postgr.es/m/3851203.1722552717@sss.pgh.pa.us

src/backend/utils/adt/jsonb.c
src/test/regress/expected/jsonb.out
src/test/regress/sql/jsonb.sql

index f4889d9ed728275f16ee00c0da7385d558bca495..8394a20e0e5e6c8dcffdeb94dc4bbaf8ac5d10bc 100644 (file)
@@ -2040,7 +2040,16 @@ jsonb_bool(PG_FUNCTION_ARGS)
    Jsonb      *in = PG_GETARG_JSONB_P(0);
    JsonbValue  v;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvBool)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "boolean");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvBool)
        cannotCastJsonbValue(v.type, "boolean");
 
    PG_FREE_IF_COPY(in, 0);
@@ -2055,7 +2064,16 @@ jsonb_numeric(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Numeric     retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "numeric");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "numeric");
 
    /*
@@ -2076,7 +2094,16 @@ jsonb_int2(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Datum       retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "smallint");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "smallint");
 
    retValue = DirectFunctionCall1(numeric_int2,
@@ -2094,7 +2121,16 @@ jsonb_int4(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Datum       retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "integer");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "integer");
 
    retValue = DirectFunctionCall1(numeric_int4,
@@ -2112,7 +2148,16 @@ jsonb_int8(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Datum       retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "bigint");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "bigint");
 
    retValue = DirectFunctionCall1(numeric_int8,
@@ -2130,7 +2175,16 @@ jsonb_float4(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Datum       retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "real");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "real");
 
    retValue = DirectFunctionCall1(numeric_float4,
@@ -2148,7 +2202,16 @@ jsonb_float8(PG_FUNCTION_ARGS)
    JsonbValue  v;
    Datum       retValue;
 
-   if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric)
+   if (!JsonbExtractScalar(&in->root, &v))
+       cannotCastJsonbValue(v.type, "double precision");
+
+   if (v.type == jbvNull)
+   {
+       PG_FREE_IF_COPY(in, 0);
+       PG_RETURN_NULL();
+   }
+
+   if (v.type != jbvNumeric)
        cannotCastJsonbValue(v.type, "double precision");
 
    retValue = DirectFunctionCall1(numeric_float8,
index 7d163a156e3f413f126898c8e024ae2b865601fa..2baff931bf23b95a18d9d905ecf94dcbf3adeef4 100644 (file)
@@ -5617,6 +5617,12 @@ select 'true'::jsonb::bool;
  t
 (1 row)
 
+select 'null'::jsonb::bool;
+ bool 
+------
+(1 row)
+
 select '[]'::jsonb::bool;
 ERROR:  cannot cast jsonb array to type boolean
 select '1.0'::jsonb::float;
@@ -5625,22 +5631,82 @@ select '1.0'::jsonb::float;
       1
 (1 row)
 
+select 'null'::jsonb::float;
+ float8 
+--------
+       
+(1 row)
+
 select '[1.0]'::jsonb::float;
 ERROR:  cannot cast jsonb array to type double precision
+select '1.0'::jsonb::float4;
+ float4 
+--------
+      1
+(1 row)
+
+select 'null'::jsonb::float4;
+ float4 
+--------
+       
+(1 row)
+
+select '[1.0]'::jsonb::float4;
+ERROR:  cannot cast jsonb array to type real
+select '12345'::jsonb::int2;
+ int2  
+-------
+ 12345
+(1 row)
+
+select 'null'::jsonb::int2;
+ int2 
+------
+     
+(1 row)
+
+select '"hello"'::jsonb::int2;
+ERROR:  cannot cast jsonb string to type smallint
 select '12345'::jsonb::int4;
  int4  
 -------
  12345
 (1 row)
 
+select 'null'::jsonb::int4;
+ int4 
+------
+     
+(1 row)
+
 select '"hello"'::jsonb::int4;
 ERROR:  cannot cast jsonb string to type integer
+select '12345'::jsonb::int8;
+ int8  
+-------
+ 12345
+(1 row)
+
+select 'null'::jsonb::int8;
+ int8 
+------
+     
+(1 row)
+
+select '"hello"'::jsonb::int8;
+ERROR:  cannot cast jsonb string to type bigint
 select '12345'::jsonb::numeric;
  numeric 
 ---------
    12345
 (1 row)
 
+select 'null'::jsonb::numeric;
+ numeric 
+---------
+        
+(1 row)
+
 select '{}'::jsonb::numeric;
 ERROR:  cannot cast jsonb object to type numeric
 select '12345.05'::jsonb::numeric;
index 5f0190d5a2b7957f107d4d6cb24125caa2f0d883..544bb610e2d65547efb89d04c9bfa9a450ffad05 100644 (file)
@@ -1540,12 +1540,25 @@ select ts_headline('[]'::jsonb, tsquery('aaa & bbb'));
 
 -- casts
 select 'true'::jsonb::bool;
+select 'null'::jsonb::bool;
 select '[]'::jsonb::bool;
 select '1.0'::jsonb::float;
+select 'null'::jsonb::float;
 select '[1.0]'::jsonb::float;
+select '1.0'::jsonb::float4;
+select 'null'::jsonb::float4;
+select '[1.0]'::jsonb::float4;
+select '12345'::jsonb::int2;
+select 'null'::jsonb::int2;
+select '"hello"'::jsonb::int2;
 select '12345'::jsonb::int4;
+select 'null'::jsonb::int4;
 select '"hello"'::jsonb::int4;
+select '12345'::jsonb::int8;
+select 'null'::jsonb::int8;
+select '"hello"'::jsonb::int8;
 select '12345'::jsonb::numeric;
+select 'null'::jsonb::numeric;
 select '{}'::jsonb::numeric;
 select '12345.05'::jsonb::numeric;
 select '12345.05'::jsonb::float4;