Close named cursor if exist, even if we didn't run execute

Close #746
This commit is contained in:
Daniele Varrazzo 2018-07-21 18:32:02 +01:00
parent a7f8c7fcde
commit 43e35db988
3 changed files with 58 additions and 9 deletions

7
NEWS
View File

@ -1,6 +1,13 @@
Current release Current release
--------------- ---------------
What's new in psycopg 2.7.6
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Close named cursors if exist, even if `~cursor.execute()` wasn't called
(:ticket:`#746`).
What's new in psycopg 2.7.5 What's new in psycopg 2.7.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -49,21 +49,22 @@
static PyObject * static PyObject *
psyco_curs_close(cursorObject *self) psyco_curs_close(cursorObject *self)
{ {
PyObject *rv = NULL;
char *lname = NULL;
EXC_IF_ASYNC_IN_PROGRESS(self, close); EXC_IF_ASYNC_IN_PROGRESS(self, close);
if (self->closed) { if (self->closed) {
rv = Py_None;
Py_INCREF(rv);
goto exit; goto exit;
} }
if (self->qname != NULL) { if (self->qname != NULL) {
char buffer[128]; const int bufsize = 255;
char buffer[bufsize + 1];
PGTransactionStatusType status; PGTransactionStatusType status;
if (!self->query) {
Dprintf("skipping named cursor close because unused");
goto close;
}
if (self->conn) { if (self->conn) {
status = PQtransactionStatus(self->conn->pgconn); status = PQtransactionStatus(self->conn->pgconn);
} }
@ -77,17 +78,45 @@ psyco_curs_close(cursorObject *self)
goto close; goto close;
} }
/* We should close a server-side cursor only if exists, or we get an
* error (#716). If we execute()d the cursor should exist alright, but
* if we didn't there is still the expectation that the cursor is
* closed (#746).
*
* So if we didn't execute() check for the cursor existence before
* closing it (the view exists since PG 8.2 according to docs).
*/
if (!self->query && self->conn->server_version >= 80200) {
if (!(lname = psycopg_escape_string(
self->conn, self->name, -1, NULL, NULL))) {
goto exit;
}
PyOS_snprintf(buffer, bufsize,
"SELECT 1 FROM pg_catalog.pg_cursors where name = %s",
lname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; }
if (self->rowcount == 0) {
Dprintf("skipping named cursor close because not existing");
goto close;
}
}
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname); PyOS_snprintf(buffer, bufsize, "CLOSE %s", self->qname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL; if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; }
} }
close: close:
self->closed = 1; self->closed = 1;
Dprintf("psyco_curs_close: cursor at %p closed", self); Dprintf("psyco_curs_close: cursor at %p closed", self);
rv = Py_None;
Py_INCREF(rv);
exit: exit:
Py_RETURN_NONE; PyMem_Free(lname);
return rv;
} }

View File

@ -448,6 +448,19 @@ class CursorTests(ConnectingTestCase):
cur = self.conn.cursor('test') cur = self.conn.cursor('test')
cur.close() cur.close()
@skip_before_postgres(8, 2)
def test_stolen_named_cursor_close(self):
cur1 = self.conn.cursor()
cur1.execute("DECLARE test CURSOR WITHOUT HOLD "
" FOR SELECT generate_series(1,7)")
cur2 = self.conn.cursor('test')
cur2.close()
cur1.execute("DECLARE test CURSOR WITHOUT HOLD "
" FOR SELECT generate_series(1,7)")
cur2 = self.conn.cursor('test')
cur2.close()
@skip_before_postgres(8, 0) @skip_before_postgres(8, 0)
def test_scroll(self): def test_scroll(self):
cur = self.conn.cursor() cur = self.conn.cursor()