diff --git a/doc/src/connection.rst b/doc/src/connection.rst index f9a1b868..235155b6 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -21,13 +21,16 @@ The ``connection`` class Connections are thread safe and can be shared among many thread. See :ref:`thread-safety` for details. - .. method:: cursor([name] [, cursor_factory]) + .. method:: cursor([name] [, cursor_factory] [, withhold]) Return a new `cursor` object using the connection. If *name* is specified, the returned cursor will be a :ref:`server side cursor ` (also known as *named cursor*). - Otherwise it will be a regular *client side* cursor. + Otherwise it will be a regular *client side* cursor. By default a + :sql:`WITHOUT HOLD` cursor is created; to create a :sql:`WITH HOLD` + cursor, pass a `!True` value as the *withhold* parameter. See + :ref:`server-side-cursors`. The name can be a string not valid as a PostgreSQL identifier: for example it may start with a digit and contain non-alphanumeric diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst index d5cf5244..c8f40abf 100644 --- a/doc/src/cursor.rst +++ b/doc/src/cursor.rst @@ -114,6 +114,19 @@ The ``cursor`` class The `name` attribute is a Psycopg extension to the |DBAPI|. + .. attribute:: withhold + + Read/write attribute: specifies if a named cursor lifetime should + extend outside of the current transaction, i.e., it is possible to + fetch from the cursor even after a `commection.commit()` (but not after + a `connection.rollback()`). See :ref:`server-side-cursors` + + .. versionadded:: 2.4.3 + + .. extension:: + + The `name` attribute is a Psycopg extension to the |DBAPI|. + .. |execute*| replace:: `execute*()` diff --git a/doc/src/usage.rst b/doc/src/usage.rst index 5f6c5b1c..e8e36ae4 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -575,6 +575,19 @@ during the iteration: the default value of 2000 allows to fetch about 100KB per roundtrip assuming records of 10-20 columns of mixed number and strings; you may decrease this value if you are dealing with huge records. +Named cursors are usually created :sql:`WITHOUT HOLD`, meaning they live only +as long as the current transaction. Trying to fetch from a named cursor after +a `~connection.commit()` or to create a named cursor when the `connection` +transaction isolation level is set to `AUTOCOMMIT` will result in an exception. +It is possible to create a :sql:`WITH HOLD` cursor by specifying a `!True` +value for the `withhold` parameter to `~connection.cursor()` or by setting the +`~cursor.withhold` attribute to `!True` before calling `~cursor.execute()` on +the cursor. It is extremely important to always `~cursor.close()` such cursors, +otherwise they will continue to hold server-side resources until the connection +will be eventually be closed. Also note that while :sql:`WITH HOLD` cursors +lifetime extends well after `~connection.commit()`, calling +`~connection.rollback()` will automatically close the cursor. + .. |DECLARE| replace:: :sql:`DECLARE` .. _DECLARE: http://www.postgresql.org/docs/9.0/static/sql-declare.html diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 1dc8232b..c7b82e82 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -42,7 +42,7 @@ /* cursor method - allocate a new cursor */ #define psyco_conn_cursor_doc \ -"cursor(cursor_factory=extensions.cursor) -- new cursor\n\n" \ +"cursor(name=None, cursor_factory=extensions.cursor, withhold=None) -- new cursor\n\n" \ "Return a new cursor.\n\nThe ``cursor_factory`` argument can be used to\n" \ "create non-standard cursors by passing a class different from the\n" \ "default. Note that the new class *should* be a sub-class of\n" \ @@ -63,16 +63,11 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds) } if (withhold != NULL) { - if (withhold == Py_True && name == NULL) { + if (PyObject_IsTrue(withhold) && name == NULL) { PyErr_SetString(ProgrammingError, "'withhold=True can be specified only for named cursors"); return NULL; } - if (withhold != NULL && withhold != Py_True && withhold != Py_False) { - PyErr_SetString(ProgrammingError, - "'withhold should be True or False"); - return NULL; - } } EXC_IF_CONN_CLOSED(self); @@ -109,7 +104,7 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds) return NULL; } - if (withhold == Py_True) + if (withhold != NULL && PyObject_IsTrue(withhold)) ((cursorObject*)obj)->withhold = 1; Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = " diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 1942cd17..18278948 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -161,8 +161,6 @@ class CursorTests(unittest.TestCase): def test_withhold(self): self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, withhold=True) - self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, - "NAME", withhold="") curs = self.conn.cursor() curs.execute("drop table if exists withhold")