mirror of
https://git.postgresql.org/git/postgresql.git
synced 2026-02-16 19:37:00 +08:00
Previously, committing or aborting inside a cursor loop was prohibited because that would close and remove the cursor. To allow that, automatically convert such cursors to holdable cursors so they survive commits or rollbacks. Portals now have a new state "auto-held", which means they have been converted automatically from pinned. An auto-held portal is kept on transaction commit or rollback, but is still removed when returning to the main loop on error. This supports all languages that have cursor loop constructs: PL/pgSQL, PL/Python, PL/Perl. Reviewed-by: Ildus Kurbangaliev <i.kurbangaliev@postgrespro.ru>
153 lines
2.7 KiB
PL/PgSQL
153 lines
2.7 KiB
PL/PgSQL
CREATE TABLE test1 (a int, b text);
|
|
|
|
|
|
CREATE PROCEDURE transaction_test1()
|
|
LANGUAGE plpythonu
|
|
AS $$
|
|
for i in range(0, 10):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
|
|
if i % 2 == 0:
|
|
plpy.commit()
|
|
else:
|
|
plpy.rollback()
|
|
$$;
|
|
|
|
CALL transaction_test1();
|
|
|
|
SELECT * FROM test1;
|
|
|
|
|
|
TRUNCATE test1;
|
|
|
|
DO
|
|
LANGUAGE plpythonu
|
|
$$
|
|
for i in range(0, 10):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
|
|
if i % 2 == 0:
|
|
plpy.commit()
|
|
else:
|
|
plpy.rollback()
|
|
$$;
|
|
|
|
SELECT * FROM test1;
|
|
|
|
|
|
TRUNCATE test1;
|
|
|
|
-- not allowed in a function
|
|
CREATE FUNCTION transaction_test2() RETURNS int
|
|
LANGUAGE plpythonu
|
|
AS $$
|
|
for i in range(0, 10):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
|
|
if i % 2 == 0:
|
|
plpy.commit()
|
|
else:
|
|
plpy.rollback()
|
|
return 1
|
|
$$;
|
|
|
|
SELECT transaction_test2();
|
|
|
|
SELECT * FROM test1;
|
|
|
|
|
|
-- also not allowed if procedure is called from a function
|
|
CREATE FUNCTION transaction_test3() RETURNS int
|
|
LANGUAGE plpythonu
|
|
AS $$
|
|
plpy.execute("CALL transaction_test1()")
|
|
return 1
|
|
$$;
|
|
|
|
SELECT transaction_test3();
|
|
|
|
SELECT * FROM test1;
|
|
|
|
|
|
-- DO block inside function
|
|
CREATE FUNCTION transaction_test4() RETURNS int
|
|
LANGUAGE plpythonu
|
|
AS $$
|
|
plpy.execute("DO LANGUAGE plpythonu $x$ plpy.commit() $x$")
|
|
return 1
|
|
$$;
|
|
|
|
SELECT transaction_test4();
|
|
|
|
|
|
-- commit inside subtransaction (prohibited)
|
|
DO LANGUAGE plpythonu $$
|
|
s = plpy.subtransaction()
|
|
s.enter()
|
|
plpy.commit()
|
|
$$;
|
|
|
|
|
|
-- commit inside cursor loop
|
|
CREATE TABLE test2 (x int);
|
|
INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
|
|
|
|
TRUNCATE test1;
|
|
|
|
DO LANGUAGE plpythonu $$
|
|
for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
|
|
plpy.commit()
|
|
$$;
|
|
|
|
SELECT * FROM test1;
|
|
|
|
-- check that this doesn't leak a holdable portal
|
|
SELECT * FROM pg_cursors;
|
|
|
|
|
|
-- error in cursor loop with commit
|
|
TRUNCATE test1;
|
|
|
|
DO LANGUAGE plpythonu $$
|
|
for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x'])
|
|
plpy.commit()
|
|
$$;
|
|
|
|
SELECT * FROM test1;
|
|
|
|
SELECT * FROM pg_cursors;
|
|
|
|
|
|
-- rollback inside cursor loop
|
|
TRUNCATE test1;
|
|
|
|
DO LANGUAGE plpythonu $$
|
|
for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
|
|
plpy.rollback()
|
|
$$;
|
|
|
|
SELECT * FROM test1;
|
|
|
|
SELECT * FROM pg_cursors;
|
|
|
|
|
|
-- first commit then rollback inside cursor loop
|
|
TRUNCATE test1;
|
|
|
|
DO LANGUAGE plpythonu $$
|
|
for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
|
|
plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
|
|
if row['x'] % 2 == 0:
|
|
plpy.commit()
|
|
else:
|
|
plpy.rollback()
|
|
$$;
|
|
|
|
SELECT * FROM test1;
|
|
|
|
SELECT * FROM pg_cursors;
|
|
|
|
|
|
DROP TABLE test1;
|
|
DROP TABLE test2;
|