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

Commit b6e5cc2

Browse files
author
Nikita Glukhov
committed
Add jsonb <=> bytea casts
1 parent f54db03 commit b6e5cc2

File tree

9 files changed

+189
-1
lines changed

9 files changed

+189
-1
lines changed

src/backend/utils/adt/jsonb.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,23 @@ jsonb_typeof(PG_FUNCTION_ARGS)
227227
PG_RETURN_TEXT_P(cstring_to_text(result));
228228
}
229229

230+
/*
231+
* jsonb_from_bytea -- bytea to jsonb conversion with validation
232+
*/
233+
Datum
234+
jsonb_from_bytea(PG_FUNCTION_ARGS)
235+
{
236+
bytea *ba = PG_GETARG_BYTEA_P(0);
237+
Jsonb *jb = (Jsonb *) ba;
238+
239+
if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
240+
ereport(ERROR,
241+
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
242+
errmsg("incorrect jsonb binary data format")));
243+
244+
PG_RETURN_JSONB_P(jb);
245+
}
246+
230247
/*
231248
* jsonb_from_cstring
232249
*

src/backend/utils/adt/jsonb_util.c

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

src/include/catalog/pg_cast.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,5 +391,8 @@ DATA(insert ( 1700 1700 1703 i f ));
391391
/* json to/from jsonb */
392392
DATA(insert ( 114 3802 0 a i ));
393393
DATA(insert ( 3802 114 0 a i ));
394+
/* jsonb to/from bytea */
395+
DATA(insert ( 3802 17 0 e b ));
396+
DATA(insert ( 17 3802 6105 e f ));
394397

395398
#endif /* PG_CAST_H */

src/include/catalog/pg_proc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4979,6 +4979,8 @@ DATA(insert OID = 3804 ( jsonb_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0
49794979
DESCR("I/O");
49804980
DATA(insert OID = 3803 ( jsonb_send PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 17 "3802" _null_ _null_ _null_ _null_ _null_ jsonb_send _null_ _null_ _null_ ));
49814981
DESCR("I/O");
4982+
DATA(insert OID = 6105 ( jsonb_from_bytea PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3802 "17" _null_ _null_ _null_ _null_ _null_ jsonb_from_bytea _null_ _null_ _null_ ));
4983+
DESCR("bytea to jsonb");
49824984

49834985
DATA(insert OID = 3263 ( jsonb_object PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3802 "1009" _null_ _null_ _null_ _null_ _null_ jsonb_object _null_ _null_ _null_ ));
49844986
DESCR("map text array of key value pairs to jsonb object");

src/include/utils/jsonb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ extern bool JsonbDeepContains(JsonbIterator **val,
393393
extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash);
394394
extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
395395
uint64 *hash, uint64 seed);
396+
extern bool JsonbValidate(const void *data, uint32 size);
396397

397398
/* jsonb.c support functions */
398399
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
@@ -4644,3 +4644,24 @@ select ts_headline('[]'::jsonb, tsquery('aaa & bbb'));
46444644
[]
46454645
(1 row)
46464646

4647+
-- test jsonb to/from bytea conversion
4648+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
4649+
bytea
4650+
------------------------------------------------------------------------------------------------------------
4651+
\x0200002001000080010000000a000010140000506162000020000000008001000200004008000090000000302000000000800200
4652+
(1 row)
4653+
4654+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
4655+
jsonb
4656+
--------------------------
4657+
{"a": 1, "b": [2, true]}
4658+
(1 row)
4659+
4660+
SELECT 'aaaa'::bytea::jsonb;
4661+
ERROR: incorrect jsonb binary data format
4662+
SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;
4663+
count
4664+
-------
4665+
0
4666+
(1 row)
4667+

src/test/regress/expected/opr_sanity.out

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,9 @@ WHERE c.castfunc = p.oid AND
846846
-- texttoxml(), which does an XML syntax check.
847847
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
848848
-- intentionally do not provide a reverse pathway for.
849+
-- As of 10.0, this finds the cast from jsonb to bytea, because those are
850+
-- binary-compatible while the reverse goes through jsonb_from_bytea(),
851+
-- which does a jsonb structure validation.
849852
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
850853
FROM pg_cast c
851854
WHERE c.castmethod = 'b' AND
@@ -864,7 +867,8 @@ WHERE c.castmethod = 'b' AND
864867
xml | text | 0 | a
865868
xml | character varying | 0 | a
866869
xml | character | 0 | a
867-
(9 rows)
870+
jsonb | bytea | 0 | e
871+
(10 rows)
868872

869873
-- **************** pg_conversion ****************
870874
-- 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
@@ -1184,3 +1184,9 @@ select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1":
11841184
select ts_headline('null'::jsonb, tsquery('aaa & bbb'));
11851185
select ts_headline('{}'::jsonb, tsquery('aaa & bbb'));
11861186
select ts_headline('[]'::jsonb, tsquery('aaa & bbb'));
1187+
1188+
-- test jsonb to/from bytea conversion
1189+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
1190+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
1191+
SELECT 'aaaa'::bytea::jsonb;
1192+
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
@@ -480,6 +480,10 @@ WHERE c.castfunc = p.oid AND
480480
-- As of 9.1, this finds the cast from pg_node_tree to text, which we
481481
-- intentionally do not provide a reverse pathway for.
482482

483+
-- As of 10.0, this finds the cast from jsonb to bytea, because those are
484+
-- binary-compatible while the reverse goes through jsonb_from_bytea(),
485+
-- which does a jsonb structure validation.
486+
483487
SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
484488
FROM pg_cast c
485489
WHERE c.castmethod = 'b' AND

0 commit comments

Comments
 (0)