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

Commit 2804aea

Browse files
author
Nikita Glukhov
committed
Add jsonb <=> bytea casts
1 parent 97d7993 commit 2804aea

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
@@ -215,6 +215,23 @@ jsonb_typeof(PG_FUNCTION_ARGS)
215215
PG_RETURN_TEXT_P(cstring_to_text(result));
216216
}
217217

218+
/*
219+
* jsonb_from_bytea -- bytea to jsonb conversion with validation
220+
*/
221+
Datum
222+
jsonb_from_bytea(PG_FUNCTION_ARGS)
223+
{
224+
bytea *ba = PG_GETARG_BYTEA_P(0);
225+
Jsonb *jb = (Jsonb *) ba;
226+
227+
if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
228+
ereport(ERROR,
229+
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
230+
errmsg("incorrect jsonb binary data format")));
231+
232+
PG_RETURN_JSONB(jb);
233+
}
234+
218235
/*
219236
* jsonb_from_cstring
220237
*

src/backend/utils/adt/jsonb_util.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,3 +1843,133 @@ uniqueifyJsonbObject(JsonbValue *object)
18431843
object->val.object.nPairs = res + 1 - object->val.object.pairs;
18441844
}
18451845
}
1846+
1847+
bool
1848+
JsonbValidate(const void *data, uint32 size)
1849+
{
1850+
const JsonbContainer *jbc = data;
1851+
const JEntry *entries;
1852+
const char *base;
1853+
uint32 header;
1854+
uint32 nentries;
1855+
uint32 offset;
1856+
uint32 i;
1857+
uint32 nkeys = 0;
1858+
1859+
check_stack_depth();
1860+
1861+
/* check header size */
1862+
if (size < offsetof(JsonbContainer, children))
1863+
return false;
1864+
1865+
size -= offsetof(JsonbContainer, children);
1866+
1867+
/* read header */
1868+
header = jbc->header;
1869+
entries = &jbc->children[0];
1870+
nentries = header & JB_CMASK;
1871+
1872+
switch (header & ~JB_CMASK)
1873+
{
1874+
case JB_FOBJECT:
1875+
nkeys = nentries;
1876+
nentries *= 2;
1877+
break;
1878+
1879+
case JB_FARRAY | JB_FSCALAR:
1880+
if (nentries != 1)
1881+
/* scalar pseudo-array must have one element */
1882+
return false;
1883+
break;
1884+
1885+
case JB_FARRAY:
1886+
break;
1887+
1888+
default:
1889+
/* invalid container type */
1890+
return false;
1891+
}
1892+
1893+
/* check JEntry array size */
1894+
if (size < sizeof(JEntry) * nentries)
1895+
return false;
1896+
1897+
size -= sizeof(JEntry) * nentries;
1898+
1899+
base = (const char *) &entries[nentries];
1900+
1901+
for (i = 0, offset = 0; i < nentries; i++)
1902+
{
1903+
JEntry entry = entries[i];
1904+
uint32 offlen = JBE_OFFLENFLD(entry);
1905+
uint32 length;
1906+
uint32 nextOffset;
1907+
1908+
if (JBE_HAS_OFF(entry))
1909+
{
1910+
nextOffset = offlen;
1911+
length = nextOffset - offset;
1912+
}
1913+
else
1914+
{
1915+
length = offlen;
1916+
nextOffset = offset + length;
1917+
}
1918+
1919+
if (nextOffset < offset || nextOffset > size)
1920+
return false;
1921+
1922+
/* check that object key is string */
1923+
if (i < nkeys && !JBE_ISSTRING(entry))
1924+
return false;
1925+
1926+
switch (entry & JENTRY_TYPEMASK)
1927+
{
1928+
case JENTRY_ISSTRING:
1929+
break;
1930+
1931+
case JENTRY_ISNUMERIC:
1932+
{
1933+
uint32 padding = INTALIGN(offset) - offset;
1934+
1935+
if (length < padding)
1936+
return false;
1937+
1938+
if (length - padding < sizeof(struct varlena))
1939+
return false;
1940+
1941+
/* TODO JsonValidateNumeric(); */
1942+
break;
1943+
}
1944+
1945+
case JENTRY_ISBOOL_FALSE:
1946+
case JENTRY_ISBOOL_TRUE:
1947+
case JENTRY_ISNULL:
1948+
if (length)
1949+
return false;
1950+
1951+
break;
1952+
1953+
case JENTRY_ISCONTAINER:
1954+
{
1955+
uint32 padding = INTALIGN(offset) - offset;
1956+
1957+
if (length < padding)
1958+
return false;
1959+
1960+
if (!JsonbValidate(&base[offset + padding],
1961+
length - padding))
1962+
return false;
1963+
1964+
break;
1965+
}
1966+
1967+
default:
1968+
return false;
1969+
}
1970+
1971+
offset = nextOffset;
1972+
}
1973+
1974+
return true;
1975+
}

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
@@ -4911,6 +4911,8 @@ DATA(insert OID = 3804 ( jsonb_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0
49114911
DESCR("I/O");
49124912
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_ ));
49134913
DESCR("I/O");
4914+
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_ ));
4915+
DESCR("bytea to jsonb");
49144916

49154917
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_ ));
49164918
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
@@ -389,6 +389,7 @@ extern Jsonb *JsonbValueToJsonb(JsonbValue *val);
389389
extern bool JsonbDeepContains(JsonbIterator **val,
390390
JsonbIterator **mContained);
391391
extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash);
392+
extern bool JsonbValidate(const void *data, uint32 size);
392393

393394
/* jsonb.c support functions */
394395
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
@@ -4260,3 +4260,24 @@ select ts_headline('[]'::jsonb, tsquery('aaa & bbb'));
42604260
[]
42614261
(1 row)
42624262

4263+
-- test jsonb to/from bytea conversion
4264+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
4265+
bytea
4266+
------------------------------------------------------------------------------------------------------------
4267+
\x0200002001000080010000000a000010140000506162000020000000008001000200004008000090000000302000000000800200
4268+
(1 row)
4269+
4270+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
4271+
jsonb
4272+
--------------------------
4273+
{"a": 1, "b": [2, true]}
4274+
(1 row)
4275+
4276+
SELECT 'aaaa'::bytea::jsonb;
4277+
ERROR: incorrect jsonb binary data format
4278+
SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j;
4279+
count
4280+
-------
4281+
0
4282+
(1 row)
4283+

src/test/regress/expected/opr_sanity.out

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

868872
-- **************** pg_conversion ****************
869873
-- 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
@@ -1099,3 +1099,9 @@ select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1":
10991099
select ts_headline('null'::jsonb, tsquery('aaa & bbb'));
11001100
select ts_headline('{}'::jsonb, tsquery('aaa & bbb'));
11011101
select ts_headline('[]'::jsonb, tsquery('aaa & bbb'));
1102+
1103+
-- test jsonb to/from bytea conversion
1104+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea;
1105+
SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb;
1106+
SELECT 'aaaa'::bytea::jsonb;
1107+
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)