2022-03-07 21:19:56 -05:00
|
|
|
CREATE EXTENSION hstore_plpython3u CASCADE;
|
2015-04-26 10:33:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test hstore -> python
|
|
|
|
|
CREATE FUNCTION test1(val hstore) RETURNS int
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
assert isinstance(val, dict)
|
2017-02-21 09:27:02 -05:00
|
|
|
plpy.info(sorted(val.items()))
|
2015-04-26 10:33:14 -04:00
|
|
|
return len(val)
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1('aa=>bb, cc=>NULL'::hstore);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- the same with the versioned language name
|
|
|
|
|
CREATE FUNCTION test1n(val hstore) RETURNS int
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
assert isinstance(val, dict)
|
2017-02-21 09:27:02 -05:00
|
|
|
plpy.info(sorted(val.items()))
|
2015-04-26 10:33:14 -04:00
|
|
|
return len(val)
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1n('aa=>bb, cc=>NULL'::hstore);
|
|
|
|
|
|
|
|
|
|
|
2023-04-27 11:55:06 -04:00
|
|
|
-- test that a non-mapping result is correctly rejected
|
|
|
|
|
CREATE FUNCTION test1bad() RETURNS hstore
|
|
|
|
|
LANGUAGE plpython3u
|
|
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
return "foo"
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1bad();
|
|
|
|
|
|
|
|
|
|
|
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.
On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().
All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.
Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
2026-06-28 22:38:39 -04:00
|
|
|
-- A mapping whose items() raises should be reported as an error, not crash
|
|
|
|
|
-- the backend
|
|
|
|
|
CREATE FUNCTION test1broken() RETURNS hstore
|
|
|
|
|
LANGUAGE plpython3u
|
|
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
class C(dict):
|
|
|
|
|
def items(self):
|
|
|
|
|
raise ValueError('items failed')
|
|
|
|
|
d = C()
|
|
|
|
|
d['x'] = 1
|
|
|
|
|
return d
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1broken();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Likewise for a mapping whose items() does not return key/value pairs
|
|
|
|
|
CREATE FUNCTION test1malformed() RETURNS hstore
|
|
|
|
|
LANGUAGE plpython3u
|
|
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
class C(dict):
|
|
|
|
|
def items(self):
|
|
|
|
|
return [42]
|
|
|
|
|
d = C()
|
|
|
|
|
d['x'] = 1
|
|
|
|
|
return d
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1malformed();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Likewise for a mapping whose items() yields fewer pairs than its length
|
|
|
|
|
CREATE FUNCTION test1short() RETURNS hstore
|
|
|
|
|
LANGUAGE plpython3u
|
|
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
class C(dict):
|
|
|
|
|
def items(self):
|
|
|
|
|
return []
|
|
|
|
|
d = C()
|
|
|
|
|
d['x'] = 1
|
|
|
|
|
return d
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1short();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Likewise for a mapping whose __len__() raises
|
|
|
|
|
CREATE FUNCTION test1brokenlen() RETURNS hstore
|
|
|
|
|
LANGUAGE plpython3u
|
|
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
class C(dict):
|
|
|
|
|
def __len__(self):
|
|
|
|
|
raise ValueError('len failed')
|
|
|
|
|
d = C()
|
|
|
|
|
d['x'] = 1
|
|
|
|
|
return d
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1brokenlen();
|
|
|
|
|
|
|
|
|
|
|
2015-04-26 10:33:14 -04:00
|
|
|
-- test hstore[] -> python
|
|
|
|
|
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
2015-05-31 07:10:45 -04:00
|
|
|
assert(val == [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}])
|
2015-04-26 10:33:14 -04:00
|
|
|
return len(val)
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test python -> hstore
|
2021-01-25 13:03:11 -05:00
|
|
|
CREATE FUNCTION test2(a int, b text) RETURNS hstore
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
2021-01-25 13:03:11 -05:00
|
|
|
val = {'a': a, 'b': b, 'c': None}
|
2015-04-26 10:33:14 -04:00
|
|
|
return val
|
|
|
|
|
$$;
|
|
|
|
|
|
2021-01-25 13:03:11 -05:00
|
|
|
SELECT test2(1, 'boo');
|
|
|
|
|
|
|
|
|
|
--- test ruleutils
|
|
|
|
|
\sf test2
|
2015-04-26 10:33:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test python -> hstore[]
|
|
|
|
|
CREATE FUNCTION test2arr() RETURNS hstore[]
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
|
|
|
|
|
return val
|
|
|
|
|
$$;
|
|
|
|
|
|
Make PL/Python handle domain-type conversions correctly.
Fix PL/Python so that it can handle domains over composite, and so that
it enforces domain constraints correctly in other cases that were not
always done properly before. Notably, it didn't do arrays of domains
right (oversight in commit c12d570fa), and it failed to enforce domain
constraints when returning a composite type containing a domain field,
and if a transform function is being used for a domain's base type then
it failed to enforce domain constraints on the result. Also, in many
places it missed checking domain constraints on null values, because
the plpy_typeio code simply wasn't called for Py_None.
Rather than try to band-aid these problems, I made a significant
refactoring of the plpy_typeio logic. The existing design of recursing
for array and composite members is extended to also treat domains as
containers requiring recursion, and the APIs for the module are cleaned
up and simplified.
The patch also modifies plpy_typeio to rely on the typcache more than
it did before (which was pretty much not at all). This reduces the
need for repetitive lookups, and lets us get rid of an ad-hoc scheme
for detecting changes in composite types. I added a couple of small
features to typcache to help with that.
Although some of this is fixing bugs that long predate v11, I don't
think we should risk a back-patch: it's a significant amount of code
churn, and there've been no complaints from the field about the bugs.
Tom Lane, reviewed by Anthony Bykov
Discussion: https://postgr.es/m/24449.1509393613@sss.pgh.pa.us
2017-11-16 16:22:57 -05:00
|
|
|
SELECT test2arr();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test python -> domain over hstore
|
|
|
|
|
CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
Make PL/Python handle domain-type conversions correctly.
Fix PL/Python so that it can handle domains over composite, and so that
it enforces domain constraints correctly in other cases that were not
always done properly before. Notably, it didn't do arrays of domains
right (oversight in commit c12d570fa), and it failed to enforce domain
constraints when returning a composite type containing a domain field,
and if a transform function is being used for a domain's base type then
it failed to enforce domain constraints on the result. Also, in many
places it missed checking domain constraints on null values, because
the plpy_typeio code simply wasn't called for Py_None.
Rather than try to band-aid these problems, I made a significant
refactoring of the plpy_typeio logic. The existing design of recursing
for array and composite members is extended to also treat domains as
containers requiring recursion, and the APIs for the module are cleaned
up and simplified.
The patch also modifies plpy_typeio to rely on the typcache more than
it did before (which was pretty much not at all). This reduces the
need for repetitive lookups, and lets us get rid of an ad-hoc scheme
for detecting changes in composite types. I added a couple of small
features to typcache to help with that.
Although some of this is fixing bugs that long predate v11, I don't
think we should risk a back-patch: it's a significant amount of code
churn, and there've been no complaints from the field about the bugs.
Tom Lane, reviewed by Anthony Bykov
Discussion: https://postgr.es/m/24449.1509393613@sss.pgh.pa.us
2017-11-16 16:22:57 -05:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
return {'a': 1, fn: 'boo', 'c': None}
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test2dom('foo');
|
|
|
|
|
SELECT test2dom('bar'); -- fail
|
2015-04-26 10:33:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test as part of prepare/execute
|
|
|
|
|
CREATE FUNCTION test3() RETURNS void
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
|
|
|
|
rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1")
|
2015-05-31 07:10:45 -04:00
|
|
|
assert(rv[0]["col1"] == {'aa': 'bb', 'cc': None})
|
2015-04-26 10:33:14 -04:00
|
|
|
|
|
|
|
|
val = {'a': 1, 'b': 'boo', 'c': None}
|
|
|
|
|
plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"])
|
|
|
|
|
rv = plpy.execute(plan, [val])
|
2015-05-31 07:10:45 -04:00
|
|
|
assert(rv[0]["col1"] == '"a"=>"1", "b"=>"boo", "c"=>NULL')
|
2015-04-26 10:33:14 -04:00
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
SELECT test3();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- test trigger
|
|
|
|
|
CREATE TABLE test1 (a int, b hstore);
|
|
|
|
|
INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL');
|
|
|
|
|
SELECT * FROM test1;
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION test4() RETURNS trigger
|
2022-03-07 21:19:56 -05:00
|
|
|
LANGUAGE plpython3u
|
2015-04-26 10:33:14 -04:00
|
|
|
TRANSFORM FOR TYPE hstore
|
|
|
|
|
AS $$
|
2015-05-31 07:10:45 -04:00
|
|
|
assert(TD["new"] == {'a': 1, 'b': {'aa': 'bb', 'cc': None}})
|
2015-04-26 10:33:14 -04:00
|
|
|
if TD["new"]["a"] == 1:
|
|
|
|
|
TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None}
|
|
|
|
|
|
|
|
|
|
return "MODIFY"
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4();
|
|
|
|
|
|
|
|
|
|
UPDATE test1 SET a = a;
|
|
|
|
|
SELECT * FROM test1;
|