Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a1d2662

Browse files
author
Nikita Glukhov
committed
Add jsonb <=> bytea casts
1 parent 82d5037 commit a1d2662

File tree

9 files changed

+194
-1
lines changed

9 files changed

+194
-1
lines changed

src/backend/utils/adt/jsonb.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,23 @@ jsonb_typeof(PG_FUNCTION_ARGS)
238238
PG_RETURN_TEXT_P(cstring_to_text(result));
239239
}
240240

241+
/*
242+
* jsonb_from_bytea -- bytea to jsonb conversion with validation
243+
*/
244+
Datum
245+
jsonb_from_bytea(PG_FUNCTION_ARGS)
246+
{
247+
bytea *ba = PG_GETARG_BYTEA_P(0);
248+
Jsonb *jb = (Jsonb *) ba;
249+
250+
if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
251+
ereport(ERROR,
252+
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
253+
errmsg("incorrect jsonb binary data format")));
254+
255+
PG_RETURN_JSONB_P(jb);
256+
}
257+
241258
/*
242259
* jsonb_from_cstring
243260
*

src/backend/utils/adt/jsonb_util.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,3 +1892,133 @@ uniqueifyJsonbObject(JsonbValue *object)
18921892
object->val.object.nPairs = res + 1 - object->val.object.pairs;
18931893
}
18941894
}
1895+
1896+
bool
1897+
JsonbValidate(const void *data, uint32 size)
1898+
{
1899+
const JsonbContainer *jbc = data;
1900+
const JEntry *entries;
1901+
const char *base;
1902+
uint32 header;
1903+
uint32 nentries;
1904+
uint32 offset;
1905+
uint32 i;
1906+
uint32 nkeys = 0;
1907+
1908+
check_stack_depth();
1909+
1910+
/* check header size */
1911+
if (size < offsetof(JsonbContainer, children))
1912+
return false;
1913+
1914+
size -= offsetof(JsonbContainer, children);
1915+
1916+
/* read header */
1917+
header = jbc->header;
1918+
entries = &jbc->children[0];
1919+
nentries = header & JB_CMASK;
1920+
1921+
switch (header & ~JB_CMASK)
1922+
{
1923+
case JB_FOBJECT:
1924+
nkeys = nentries;
1925+
nentries *= 2;
1926+
break;
1927+
1928+
case JB_FARRAY | JB_FSCALAR:
1929+
if (nentries != 1)
1930+
/* scalar pseudo-array must have one element */
1931+
return false;
1932+
break;
1933+
1934+
case JB_FARRAY:
1935+
break;
1936+
1937+
default:
1938+
/* invalid container type */
1939+
return false;
1940+
}
1941+
1942+
/* check JEntry array size */
1943+
if (size < sizeof(JEntry) * nentries)
1944+
return false;
1945+
1946+
size -= sizeof(JEntry) * nentries;
1947+
1948+
base = (const char *) &entries[nentries];
1949+
1950+
for (i = 0, offset = 0; i < nentries; i++)
1951+
{
1952+
JEntry entry = entries[i];
1953+
uint32 offlen = JBE_OFFLENFLD(entry);
1954+
uint32 length;
1955+
uint32 nextOffset;
1956+
1957+
if (JBE_HAS_OFF(entry))
1958+
{
1959+
nextOffset = offlen;
1960+
length = nextOffset - offset;
1961+
}
1962+
else
1963+
{
1964+
length = offlen;
1965+
nextOffset = offset + length;
1966+
}
1967+
1968+
if (nextOffset < offset || nextOffset > size)
1969+
return false;
1970+
1971+
/* check that object key is string */
1972+
if (i < nkeys && !JBE_ISSTRING(entry))
1973+
return false;
1974+
1975+
switch (entry & JENTRY_TYPEMASK)
1976+
{
1977+
case JENTRY_ISSTRING:
1978+
break;
1979+
1980+
case JENTRY_ISNUMERIC:
1981+
{
1982+
uint32 padding = INTALIGN(offset) - offset;
1983+
1984+
if (length < padding)
1985+
return false;
1986+
1987+
if (length - padding < sizeof(struct varlena))
1988+
return false;
1989+
1990+
/* TODO JsonValidateNumeric(); */
1991+
break;
1992+
}
1993+
1994+
case JENTRY_ISBOOL_FALSE:
1995+
case JENTRY_ISBOOL_TRUE:
1996+
case JENTRY_ISNULL:
1997+
if (length)
1998+
return false;
1999+
2000+
break;
2001+
2002+
case JENTRY_ISCONTAINER:
2003+
{
2004+
uint32 padding = INTALIGN(offset) - offset;
2005+
2006+
if (length < padding)
2007+
return false;
2008+
2009+
if (!JsonbValidate(&base[offset + padding],
2010+
length - padding))
2011+
return false;
2012+
2013+
break;
2014+
}
2015+
2016+
default:
2017+
return false;
2018+
}
2019+
2020+
offset = nextOffset;
2021+
}
2022+
2023+
return true;
2024+
}

src/include/catalog/pg_cast.dat

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,13 @@
496496
{ castsource => 'jsonb', casttarget => 'json', castfunc => '0',
497497
castcontext => 'a', castmethod => 'i' },
498498

499+
# jsonb to/from bytea
500+
{ castsource => 'jsonb', casttarget => 'bytea', castfunc => '0',
501+
castcontext => 'e', castmethod => 'b' },
502+
{ castsource => 'bytea', casttarget => 'jsonb',
503+
castfunc => 'jsonb_from_bytea(bytea)',
504+
castcontext => 'e', castmethod => 'f' },
505+
499506
# jsonb to numeric and bool types
500507
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
501508
castcontext => 'e', castmethod => 'f' },

src/include/catalog/pg_proc.dat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9131,6 +9131,9 @@
91319131
{ oid => '6062', descr => 'check jsonb value type',
91329132
proname => 'jsonb_is_valid', prorettype => 'bool',
91339133
proargtypes => 'jsonb text', prosrc => 'jsonb_is_valid' },
9134+
{ oid => '6116', descr => 'bytea to jsonb',
9135+
proname => 'jsonb_from_bytea', prorettype => 'jsonb', proargtypes => 'bytea',
9136+
prosrc => 'jsonb_from_bytea' },
91349137

91359138
{ oid => '3478',
91369139
proname => 'jsonb_object_field', prorettype => 'jsonb',

src/include/utils/jsonb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ extern bool JsonbDeepContains(JsonbIterator **val,
388388
extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash);
389389
extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
390390
uint64 *hash, uint64 seed);
391+
extern bool JsonbValidate(const void *data, uint32 size);
391392

392393
/* jsonb.c support functions */
393394
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,

src/test/regress/expected/jsonb.out

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4930,3 +4930,24 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
49304930
12345
49314931
(1 row)
49324932

4933+
-- test jsonb to/from bytea conversion
4934+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
4935+
bytea
4936+
------------------------------------------------------------------------------------------------------------
4937+
\x0200002001000080010000000a000010140000506162000020000000008001000200004008000090000000302000000000800200
4938+
(1 row)
4939+
4940+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
4941+
jsonb
4942+
--------------------------
4943+
{"a": 1, "b": [2, true]}
4944+
(1 row)
4945+
4946+
SELECT 'aaaa'::bytea::jsonb;
4947+
ERROR: incorrect jsonb binary data format
4948+
SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;
4949+
count
4950+
-------
4951+
0
4952+
(1 row)
4953+

src/test/regress/expected/opr_sanity.out

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,9 @@ WHERE c.castfunc = p.oid AND
879879
-- texttoxml(), which does an XML syntax check.
880880
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
881881
-- intentionally do not provide a reverse pathway for.
882+
-- As of 10.0, this finds the cast from jsonb to bytea, because those are
883+
-- binary-compatible while the reverse goes through jsonb_from_bytea(),
884+
-- which does a jsonb structure validation.
882885
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
883886
FROM pg_cast c
884887
WHERE c.castmethod = 'b' AND
@@ -898,7 +901,8 @@ WHERE c.castmethod = 'b' AND
898901
xml | text | 0 | a
899902
xml | character varying | 0 | a
900903
xml | character | 0 | a
901-
(10 rows)
904+
jsonb | bytea | 0 | e
905+
(11 rows)
902906

903907
-- **************** pg_conversion ****************
904908
-- Look for illegal values in pg_conversion fields.

src/test/regress/sql/jsonb.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,3 +1250,9 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
12501250
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
12511251
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
12521252
select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
1253+
1254+
-- test jsonb to/from bytea conversion
1255+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
1256+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
1257+
SELECT 'aaaa'::bytea::jsonb;
1258+
SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;

src/test/regress/sql/opr_sanity.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,10 @@ WHERE c.castfunc = p.oid AND
485485
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
486486
-- intentionally do not provide a reverse pathway for.
487487

488+
-- As of 10.0, this finds the cast from jsonb to bytea, because those are
489+
-- binary-compatible while the reverse goes through jsonb_from_bytea(),
490+
-- which does a jsonb structure validation.
491+
488492
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
489493
FROM pg_cast c
490494
WHERE c.castmethod = 'b' AND

0 commit comments

Comments
 (0)