I wrote:
> OK. Nobody has spoken against the 0001 patch (replace errors with
> return-a-null), so I think I'll go ahead and commit that one.
> Then I'll return to this thread with a fleshed-out patch for 0002.
0001 is pushed, and as promised, here's a version of 0002 extended
to cover all the string-category types.
However ... the more I play with 0002, the less enchanted I get.
The problem is that there are two different use-cases to serve:
1. General conversion of any jsonb value to string form.
2. Conversion of a jsonb scalar string to a SQL string.
For use-case #1, it makes sense that we do
regression=# select '"hello"'::jsonb::text;
text
---------
"hello"
(1 row)
but for use-case #2, you'd probably prefer that the quotes
weren't included. (We can't do that in use-case #1 because
the string contents might look too much like some other
sort of JSON value.) So it seems like two separate conversion
functions are needed to serve these two use-cases, and for
better or worse we've already decided that casting jsonb to text
is meant for use-case #1. (It was sort of a decision by default,
I suspect, but not deciding is still a decision.)
What I am realizing is that "JSON null becomes SQL NULL" is a rule
that is adapted to use-case #2 but not so much to use-case #1.
For example, the existing behavior
regression=# select null::jsonb::text;
text
------
(1 row)
regression=# select 'null'::jsonb::text;
text
------
null
(1 row)
actually makes plenty of sense if you hope to be able to round-trip
the result. It's only after rejecting non-scalar JSON values that
it makes sense to special-case a JSON null.
So here's the patch, just because I promised it, but I'm now
thinking about withdrawing it.
What would make more sense for use-case #2 is something that
produces NULL for JSON null, a de-quoted string for a JSON
string value, and an error otherwise. The ->> operator is
about halfway there (it won't throw an error for non-scalar
input), but of course that only works when the value you want
to extract is in a JSON object field. I guess what would
be wanted is a new function f(jsonb) returns text, but I'm
unsure about a good name.
regards, tom lane
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 8394a20e0e..e38aebd848 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2034,6 +2034,10 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
elog(ERROR, "unknown jsonb type: %d", (int) type);
}
+/*
+ * Assorted jsonb-to-scalar-type conversion functions.
+ */
+
Datum
jsonb_bool(PG_FUNCTION_ARGS)
{
@@ -2222,6 +2226,74 @@ jsonb_float8(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(retValue);
}
+/* Note: this is also used for jsonb-to-varchar */
+Datum
+jsonb_text(PG_FUNCTION_ARGS)
+{
+ Jsonb *in = PG_GETARG_JSONB_P(0);
+ JsonbValue v;
+ StringInfoData jtext;
+
+ /* Convert scalar null to SQL null */
+ if (JsonbExtractScalar(&in->root, &v) && v.type == jbvNull)
+ {
+ PG_FREE_IF_COPY(in, 0);
+ PG_RETURN_NULL();
+ }
+
+ /* Every other case acts like jsonb_out() */
+ initStringInfo(&jtext);
+ (void) JsonbToCString(&jtext, &in->root, VARSIZE(in));
+
+ PG_FREE_IF_COPY(in, 0);
+
+ PG_RETURN_TEXT_P(cstring_to_text_with_len(jtext.data, jtext.len));
+}
+
+Datum
+jsonb_bpchar(PG_FUNCTION_ARGS)
+{
+ /*
+ * This is really equivalent to jsonb_text, but it must be a separate C
+ * function to keep opr_sanity.sql from complaining.
+ */
+ return jsonb_text(fcinfo);
+}
+
+Datum
+jsonb_name(PG_FUNCTION_ARGS)
+{
+ Jsonb *in = PG_GETARG_JSONB_P(0);
+ Name result;
+ JsonbValue v;
+ StringInfoData jtext;
+ int len;
+
+ /* Convert scalar null to SQL null */
+ if (JsonbExtractScalar(&in->root, &v) && v.type == jbvNull)
+ {
+ PG_FREE_IF_COPY(in, 0);
+ PG_RETURN_NULL();
+ }
+
+ /* Every other case acts like jsonb_out() */
+ initStringInfo(&jtext);
+ (void) JsonbToCString(&jtext, &in->root, VARSIZE(in));
+
+ PG_FREE_IF_COPY(in, 0);
+
+ /* Truncate oversize input */
+ len = jtext.len;
+ if (len >= NAMEDATALEN)
+ len = pg_mbcliplen(jtext.data, len, NAMEDATALEN - 1);
+
+ /* We use palloc0 here to ensure result is zero-padded */
+ result = (Name) palloc0(NAMEDATALEN);
+ memcpy(NameStr(*result), jtext.data, len);
+
+ PG_RETURN_NAME(result);
+}
+
/*
* Convert jsonb to a C-string stripping quotes from scalar strings.
*/
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index a26ba34e86..1bd40d8fac 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,7 +512,7 @@
{ castsource => 'jsonb', casttarget => 'json', castfunc => '0',
castcontext => 'a', castmethod => 'i' },
-# jsonb to numeric and bool types
+# jsonb to various scalar types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
castcontext => 'e', castmethod => 'f' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
@@ -527,6 +527,15 @@
castcontext => 'e', castmethod => 'f' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
castcontext => 'e', castmethod => 'f' },
+# these casts replace implicit COERCEVIAIO casts, so must be assignment-level:
+{ castsource => 'jsonb', casttarget => 'text', castfunc => 'text(jsonb)',
+ castcontext => 'a', castmethod => 'f' },
+{ castsource => 'jsonb', casttarget => 'varchar', castfunc => 'varchar(jsonb)',
+ castcontext => 'a', castmethod => 'f' },
+{ castsource => 'jsonb', casttarget => 'bpchar', castfunc => 'bpchar(jsonb)',
+ castcontext => 'a', castmethod => 'f' },
+{ castsource => 'jsonb', casttarget => 'name', castfunc => 'name(jsonb)',
+ castcontext => 'a', castmethod => 'f' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 18560755d2..350ba1f800 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4705,6 +4705,18 @@
{ oid => '2580', descr => 'convert jsonb to float8',
proname => 'float8', prorettype => 'float8', proargtypes => 'jsonb',
prosrc => 'jsonb_float8' },
+{ oid => '8079', descr => 'convert jsonb to text',
+ proname => 'text', prorettype => 'text', proargtypes => 'jsonb',
+ prosrc => 'jsonb_text' },
+{ oid => '8080', descr => 'convert jsonb to varchar',
+ proname => 'varchar', prorettype => 'varchar', proargtypes => 'jsonb',
+ prosrc => 'jsonb_text' },
+{ oid => '8081', descr => 'convert jsonb to char(n)',
+ proname => 'bpchar', prorettype => 'bpchar', proargtypes => 'jsonb',
+ prosrc => 'jsonb_bpchar' },
+{ oid => '8082', descr => 'convert jsonb to name',
+ proname => 'name', prorettype => 'name', proargtypes => 'jsonb',
+ prosrc => 'jsonb_name' },
# formatting
{ oid => '1770', descr => 'format timestamp with time zone to text',
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf..d7a3bc299c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5781,3 +5781,75 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
12345
(1 row)
+select 'true'::jsonb::text;
+ text
+------
+ true
+(1 row)
+
+select '1.0'::jsonb::text;
+ text
+------
+ 1.0
+(1 row)
+
+select '"hello"'::jsonb::text;
+ text
+---------
+ "hello"
+(1 row)
+
+select 'null'::jsonb::text;
+ text
+------
+
+(1 row)
+
+select '[1.0]'::jsonb::text;
+ text
+-------
+ [1.0]
+(1 row)
+
+select '{"a": "b"}'::jsonb::text;
+ text
+------------
+ {"a": "b"}
+(1 row)
+
+select 'true'::jsonb::varchar;
+ varchar
+---------
+ true
+(1 row)
+
+select '1.0'::jsonb::varchar;
+ varchar
+---------
+ 1.0
+(1 row)
+
+select '"hello"'::jsonb::varchar;
+ varchar
+---------
+ "hello"
+(1 row)
+
+select 'null'::jsonb::varchar;
+ varchar
+---------
+
+(1 row)
+
+select '[1.0]'::jsonb::varchar;
+ varchar
+---------
+ [1.0]
+(1 row)
+
+select '{"a": "b"}'::jsonb::varchar;
+ varchar
+------------
+ {"a": "b"}
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2..b391b1555a 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1572,3 +1572,15 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+select 'true'::jsonb::text;
+select '1.0'::jsonb::text;
+select '"hello"'::jsonb::text;
+select 'null'::jsonb::text;
+select '[1.0]'::jsonb::text;
+select '{"a": "b"}'::jsonb::text;
+select 'true'::jsonb::varchar;
+select '1.0'::jsonb::varchar;
+select '"hello"'::jsonb::varchar;
+select 'null'::jsonb::varchar;
+select '[1.0]'::jsonb::varchar;
+select '{"a": "b"}'::jsonb::varchar;