1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-17 06:41:09 +03:00

Allow committing inside cursor loop

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>
This commit is contained in:
Peter Eisentraut
2018-03-28 18:57:10 -04:00
parent a2894cce54
commit 056a5a3f63
15 changed files with 534 additions and 87 deletions

View File

@ -111,11 +111,44 @@ for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
plpy.commit()
$$;
ERROR: cannot commit transaction while a cursor is open
CONTEXT: PL/Python anonymous code block
SELECT * FROM test1;
a | b
---+---
0 |
1 |
2 |
3 |
4 |
(5 rows)
-- check that this doesn't leak a holdable portal
SELECT * FROM pg_cursors;
name | statement | is_holdable | is_binary | is_scrollable | creation_time
------+-----------+-------------+-----------+---------------+---------------
(0 rows)
-- 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()
$$;
ERROR: spiexceptions.DivisionByZero: division by zero
CONTEXT: Traceback (most recent call last):
PL/Python anonymous code block, line 3, in <module>
plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x'])
PL/Python anonymous code block
SELECT * FROM test1;
a | b
-----+---
-6 |
-12 |
(2 rows)
SELECT * FROM pg_cursors;
name | statement | is_holdable | is_binary | is_scrollable | creation_time
------+-----------+-------------+-----------+---------------+---------------
(0 rows)
-- rollback inside cursor loop
@ -125,12 +158,38 @@ for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
plpy.rollback()
$$;
ERROR: cannot abort transaction while a cursor is open
CONTEXT: PL/Python anonymous code block
SELECT * FROM test1;
a | b
---+---
(0 rows)
SELECT * FROM pg_cursors;
name | statement | is_holdable | is_binary | is_scrollable | creation_time
------+-----------+-------------+-----------+---------------+---------------
(0 rows)
-- 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;
a | b
---+---
0 |
2 |
4 |
(3 rows)
SELECT * FROM pg_cursors;
name | statement | is_holdable | is_binary | is_scrollable | creation_time
------+-----------+-------------+-----------+---------------+---------------
(0 rows)
DROP TABLE test1;
DROP TABLE test2;

View File

@ -594,10 +594,7 @@ PLy_commit(PyObject *self, PyObject *args)
{
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (ThereArePinnedPortals())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot commit transaction while a cursor is open")));
HoldPinnedPortals();
SPI_commit();
SPI_start_transaction();
@ -613,10 +610,7 @@ PLy_rollback(PyObject *self, PyObject *args)
{
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (ThereArePinnedPortals())
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
errmsg("cannot abort transaction while a cursor is open")));
HoldPinnedPortals();
SPI_rollback();
SPI_start_transaction();

View File

@ -99,6 +99,23 @@ $$;
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;
@ -111,6 +128,25 @@ $$;
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;