mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-29 04:13:43 +03:00
parent
d8e6426433
commit
e5ad0ab2d9
2
NEWS
2
NEWS
|
@ -5,6 +5,8 @@ What's new in psycopg 2.9
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
|
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
|
||||||
|
- ``with connection`` starts a transaction on autocommit transactions too
|
||||||
|
(:ticket:`#941`).
|
||||||
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
||||||
`~psycopg2.OperationalError` (a subclass of the previously used
|
`~psycopg2.OperationalError` (a subclass of the previously used
|
||||||
`~psycopg2.DatabaseError`) (:ticket:`#1148`).
|
`~psycopg2.DatabaseError`) (:ticket:`#1148`).
|
||||||
|
|
|
@ -832,6 +832,9 @@ and each ``with`` block is effectively wrapped in a separate transaction::
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
``with connection`` starts a transaction also on autocommit connections.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Server side; Cursor
|
pair: Server side; Cursor
|
||||||
|
|
|
@ -145,6 +145,9 @@ struct connectionObject {
|
||||||
|
|
||||||
/* the pid this connection was created into */
|
/* the pid this connection was created into */
|
||||||
pid_t procpid;
|
pid_t procpid;
|
||||||
|
|
||||||
|
/* inside a with block */
|
||||||
|
int entered;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* map isolation level values into a numeric const */
|
/* map isolation level values into a numeric const */
|
||||||
|
|
|
@ -406,10 +406,22 @@ psyco_conn_tpc_recover(connectionObject *self, PyObject *dummy)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
psyco_conn_enter(connectionObject *self, PyObject *dummy)
|
psyco_conn_enter(connectionObject *self, PyObject *dummy)
|
||||||
{
|
{
|
||||||
|
PyObject *rv = NULL;
|
||||||
|
|
||||||
EXC_IF_CONN_CLOSED(self);
|
EXC_IF_CONN_CLOSED(self);
|
||||||
|
|
||||||
|
if (self->entered) {
|
||||||
|
PyErr_SetString(ProgrammingError,
|
||||||
|
"the connection cannot be re-entered recursively");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->entered = 1;
|
||||||
Py_INCREF(self);
|
Py_INCREF(self);
|
||||||
return (PyObject *)self;
|
rv = (PyObject *)self;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -427,6 +439,9 @@ psyco_conn_exit(connectionObject *self, PyObject *args)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* even if there will be an error, consider ourselves out */
|
||||||
|
self->entered = 0;
|
||||||
|
|
||||||
if (type == Py_None) {
|
if (type == Py_None) {
|
||||||
if (!(tmp = PyObject_CallMethod((PyObject *)self, "commit", NULL))) {
|
if (!(tmp = PyObject_CallMethod((PyObject *)self, "commit", NULL))) {
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
|
@ -347,14 +347,19 @@ pq_begin_locked(connectionObject *conn, PyThreadState **tstate)
|
||||||
char buf[256]; /* buf size must be same as bufsize */
|
char buf[256]; /* buf size must be same as bufsize */
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
Dprintf("pq_begin_locked: pgconn = %p, autocommit = %d, status = %d",
|
Dprintf("pq_begin_locked: pgconn = %p, %d, status = %d",
|
||||||
conn->pgconn, conn->autocommit, conn->status);
|
conn->pgconn, conn->autocommit, conn->status);
|
||||||
|
|
||||||
if (conn->autocommit || conn->status != CONN_STATUS_READY) {
|
if (conn->status != CONN_STATUS_READY) {
|
||||||
Dprintf("pq_begin_locked: transaction in progress");
|
Dprintf("pq_begin_locked: transaction in progress");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conn->autocommit && !conn->entered) {
|
||||||
|
Dprintf("pq_begin_locked: autocommit and no with block");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (conn->isolevel == ISOLATION_LEVEL_DEFAULT
|
if (conn->isolevel == ISOLATION_LEVEL_DEFAULT
|
||||||
&& conn->readonly == STATE_DEFAULT
|
&& conn->readonly == STATE_DEFAULT
|
||||||
&& conn->deferrable == STATE_DEFAULT) {
|
&& conn->deferrable == STATE_DEFAULT) {
|
||||||
|
@ -393,10 +398,10 @@ pq_commit(connectionObject *conn)
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
pthread_mutex_lock(&conn->lock);
|
pthread_mutex_lock(&conn->lock);
|
||||||
|
|
||||||
Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d",
|
Dprintf("pq_commit: pgconn = %p, status = %d",
|
||||||
conn->pgconn, conn->autocommit, conn->status);
|
conn->pgconn, conn->status);
|
||||||
|
|
||||||
if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
|
if (conn->status != CONN_STATUS_BEGIN) {
|
||||||
Dprintf("pq_commit: no transaction to commit");
|
Dprintf("pq_commit: no transaction to commit");
|
||||||
retvalue = 0;
|
retvalue = 0;
|
||||||
}
|
}
|
||||||
|
@ -427,10 +432,10 @@ pq_abort_locked(connectionObject *conn, PyThreadState **tstate)
|
||||||
{
|
{
|
||||||
int retvalue = -1;
|
int retvalue = -1;
|
||||||
|
|
||||||
Dprintf("pq_abort_locked: pgconn = %p, autocommit = %d, status = %d",
|
Dprintf("pq_abort_locked: pgconn = %p, status = %d",
|
||||||
conn->pgconn, conn->autocommit, conn->status);
|
conn->pgconn, conn->status);
|
||||||
|
|
||||||
if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) {
|
if (conn->status != CONN_STATUS_BEGIN) {
|
||||||
Dprintf("pq_abort_locked: no transaction to abort");
|
Dprintf("pq_abort_locked: no transaction to abort");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -488,12 +493,12 @@ pq_reset_locked(connectionObject *conn, PyThreadState **tstate)
|
||||||
{
|
{
|
||||||
int retvalue = -1;
|
int retvalue = -1;
|
||||||
|
|
||||||
Dprintf("pq_reset_locked: pgconn = %p, autocommit = %d, status = %d",
|
Dprintf("pq_reset_locked: pgconn = %p, status = %d",
|
||||||
conn->pgconn, conn->autocommit, conn->status);
|
conn->pgconn, conn->status);
|
||||||
|
|
||||||
conn->mark += 1;
|
conn->mark += 1;
|
||||||
|
|
||||||
if (!conn->autocommit && conn->status == CONN_STATUS_BEGIN) {
|
if (conn->status == CONN_STATUS_BEGIN) {
|
||||||
retvalue = pq_execute_command_locked(conn, "ABORT", tstate);
|
retvalue = pq_execute_command_locked(conn, "ABORT", tstate);
|
||||||
if (retvalue != 0) return retvalue;
|
if (retvalue != 0) return retvalue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,93 @@ class WithConnectionTestCase(WithTestCase):
|
||||||
curs.execute("select * from test_with")
|
curs.execute("select * from test_with")
|
||||||
self.assertEqual(curs.fetchall(), [])
|
self.assertEqual(curs.fetchall(), [])
|
||||||
|
|
||||||
|
def test_cant_reenter(self):
|
||||||
|
raised_ok = False
|
||||||
|
with self.conn:
|
||||||
|
try:
|
||||||
|
with self.conn:
|
||||||
|
pass
|
||||||
|
except psycopg2.ProgrammingError:
|
||||||
|
raised_ok = True
|
||||||
|
|
||||||
|
self.assert_(raised_ok)
|
||||||
|
|
||||||
|
# Still good
|
||||||
|
with self.conn:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_with_autocommit(self):
|
||||||
|
self.conn.autocommit = True
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
with self.conn:
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
curs.execute("insert into test_with values (1)")
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status,
|
||||||
|
ext.TRANSACTION_STATUS_INTRANS,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
curs.execute("select count(*) from test_with")
|
||||||
|
self.assertEqual(curs.fetchone()[0], 1)
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_autocommit_pyerror(self):
|
||||||
|
self.conn.autocommit = True
|
||||||
|
raised_ok = False
|
||||||
|
try:
|
||||||
|
with self.conn:
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
curs.execute("insert into test_with values (2)")
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status,
|
||||||
|
ext.TRANSACTION_STATUS_INTRANS,
|
||||||
|
)
|
||||||
|
1 / 0
|
||||||
|
except ZeroDivisionError:
|
||||||
|
raised_ok = True
|
||||||
|
|
||||||
|
self.assert_(raised_ok)
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
curs.execute("select count(*) from test_with")
|
||||||
|
self.assertEqual(curs.fetchone()[0], 0)
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_autocommit_pgerror(self):
|
||||||
|
self.conn.autocommit = True
|
||||||
|
raised_ok = False
|
||||||
|
try:
|
||||||
|
with self.conn:
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
curs.execute("insert into test_with values (2)")
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status,
|
||||||
|
ext.TRANSACTION_STATUS_INTRANS,
|
||||||
|
)
|
||||||
|
curs.execute("insert into test_with values ('x')")
|
||||||
|
except psycopg2.errors.InvalidTextRepresentation:
|
||||||
|
raised_ok = True
|
||||||
|
|
||||||
|
self.assert_(raised_ok)
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
curs.execute("select count(*) from test_with")
|
||||||
|
self.assertEqual(curs.fetchone()[0], 0)
|
||||||
|
self.assertEqual(
|
||||||
|
self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithCursorTestCase(WithTestCase):
|
class WithCursorTestCase(WithTestCase):
|
||||||
def test_with_ok(self):
|
def test_with_ok(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user