mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-22 00:46:33 +03:00
Fixed bug #194 (and added nice MD project not that C/C++ is supported.)
This commit is contained in:
parent
0422506404
commit
c9e701baa9
|
@ -1,3 +1,11 @@
|
|||
2007-09-08 Federico Di Gregorio <fog@initd.org>
|
||||
|
||||
* Added MonoDevelop project, yahi!
|
||||
|
||||
2007-09-06 Federico Di Gregorio <fog@initd.org>
|
||||
|
||||
* Fixed bug #194.
|
||||
|
||||
2007-09-01 Federico Di Gregorio <fog@initd.org>
|
||||
|
||||
* Added "name" parameter to all .cursor() calls in extras.py.
|
||||
|
|
|
@ -40,8 +40,10 @@ conn_notice_callback(void *args, const char *message)
|
|||
Dprintf("conn_notice_callback: %s", message);
|
||||
|
||||
/* unfortunately the old protocl return COPY FROM errors only as notices,
|
||||
so we need to filter them looking for such errors */
|
||||
if (strncmp(message, "ERROR", 5) == 0)
|
||||
so we need to filter them looking for such errors (but we do it
|
||||
only if the protocol if <3, else we don't need that */
|
||||
|
||||
if (self->protocol < 3 && strncmp(message, "ERROR", 5) == 0)
|
||||
pq_set_critical(self, message);
|
||||
else
|
||||
PyList_Append(self->notice_list, PyString_FromString(message));
|
||||
|
@ -208,6 +210,8 @@ conn_commit(connectionObject *self)
|
|||
pthread_mutex_unlock(&self->lock);
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (res == -1)
|
||||
pq_resolve_critical(self, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -227,6 +231,8 @@ conn_rollback(connectionObject *self)
|
|||
pthread_mutex_unlock(&self->lock);
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (res == -1)
|
||||
pq_resolve_critical(self, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,10 +148,27 @@ pq_set_critical(connectionObject *conn, const char *msg)
|
|||
if (msg == NULL)
|
||||
msg = PQerrorMessage(conn->pgconn);
|
||||
if (conn->critical) free(conn->critical);
|
||||
Dprintf("pq_set_critical: setting %s", msg);
|
||||
if (msg && msg[0] != '\0') conn->critical = strdup(msg);
|
||||
else conn->critical = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
pq_clear_critical(connectionObject *conn)
|
||||
{
|
||||
/* sometimes we know that the notice analizer set a critical that
|
||||
was not really as such (like when raising an error for a delayed
|
||||
contraint violation. it would be better to analyze the notice
|
||||
or avoid the set-error-on-notice stuff at all but given that we
|
||||
can't, some functions at least clear the critical status after
|
||||
operations they know would result in a wrong critical to be set */
|
||||
Dprintf("pq_clear_critical: clearing %s", conn->critical);
|
||||
if (conn->critical) {
|
||||
free(conn->critical);
|
||||
conn->critical = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *
|
||||
pq_resolve_critical(connectionObject *conn, int close)
|
||||
{
|
||||
|
@ -167,6 +184,9 @@ pq_resolve_critical(connectionObject *conn, int close)
|
|||
|
||||
/* we don't want to destroy this connection but just close it */
|
||||
if (close == 1) conn_close(conn);
|
||||
|
||||
/* remember to clear the critical! */
|
||||
pq_clear_critical(conn);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -268,6 +288,9 @@ pq_commit(connectionObject *conn)
|
|||
pgstatus = PQresultStatus(pgres);
|
||||
if (pgstatus != PGRES_COMMAND_OK ) {
|
||||
Dprintf("pq_commit: result is NOT OK");
|
||||
/* if the result is not OK the transaction has been rolled back
|
||||
so we set the status to CONN_STATUS_READY anyway */
|
||||
conn->status = CONN_STATUS_READY;
|
||||
pq_set_critical(conn, NULL);
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -400,8 +423,7 @@ pq_execute(cursorObject *curs, const char *query, int async)
|
|||
if (pq_begin(curs->conn) < 0) {
|
||||
pthread_mutex_unlock(&(curs->conn->lock));
|
||||
Py_BLOCK_THREADS;
|
||||
PyErr_SetString(OperationalError,
|
||||
PQerrorMessage(curs->conn->pgconn));
|
||||
pq_resolve_critical(curs->conn, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
21
tests/__init__.py
Normal file
21
tests/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import os
|
||||
import unittest
|
||||
|
||||
dbname = os.environ.get('PSYCOPG2_TESTDB', 'test')
|
||||
|
||||
import test_psycopg2_dbapi20
|
||||
import test_transaction
|
||||
import types_basic
|
||||
import extras_dictcursor
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(test_psycopg2_dbapi20.test_suite())
|
||||
suite.addTest(test_transaction.test_suite())
|
||||
suite.addTest(types_basic.test_suite())
|
||||
suite.addTest(extras_dictcursor.test_suite())
|
||||
return suite
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
76
tests/test_transaction.py
Normal file
76
tests/test_transaction.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import psycopg2
|
||||
import unittest
|
||||
import tests
|
||||
|
||||
from psycopg2.extensions import (
|
||||
ISOLATION_LEVEL_SERIALIZABLE, STATUS_BEGIN, STATUS_READY)
|
||||
|
||||
class TransactionTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.conn = psycopg2.connect("dbname=%s" % tests.dbname)
|
||||
self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
|
||||
curs = self.conn.cursor()
|
||||
curs.execute('''
|
||||
CREATE TEMPORARY TABLE table1 (
|
||||
id int PRIMARY KEY
|
||||
)''')
|
||||
# The constraint is set to deferrable for the commit_failed test
|
||||
curs.execute('''
|
||||
CREATE TEMPORARY TABLE table2 (
|
||||
id int PRIMARY KEY,
|
||||
table1_id int,
|
||||
CONSTRAINT table2__table1_id__fk
|
||||
FOREIGN KEY (table1_id) REFERENCES table1(id) DEFERRABLE)''')
|
||||
curs.execute('INSERT INTO table1 VALUES (1)')
|
||||
curs.execute('INSERT INTO table2 VALUES (1, 1)')
|
||||
self.conn.commit()
|
||||
|
||||
def tearDown(self):
|
||||
self.conn.close()
|
||||
|
||||
def test_rollback(self):
|
||||
# Test that rollback undoes changes
|
||||
curs = self.conn.cursor()
|
||||
curs.execute('INSERT INTO table2 VALUES (2, 1)')
|
||||
# Rollback takes us from BEGIN state to READY state
|
||||
self.assertEqual(self.conn.status, STATUS_BEGIN)
|
||||
self.conn.rollback()
|
||||
self.assertEqual(self.conn.status, STATUS_READY)
|
||||
curs.execute('SELECT id, table1_id FROM table2 WHERE id = 2')
|
||||
self.assertEqual(curs.fetchall(), [])
|
||||
|
||||
def test_commit(self):
|
||||
# Test that commit stores changes
|
||||
curs = self.conn.cursor()
|
||||
curs.execute('INSERT INTO table2 VALUES (2, 1)')
|
||||
# Rollback takes us from BEGIN state to READY state
|
||||
self.assertEqual(self.conn.status, STATUS_BEGIN)
|
||||
self.conn.commit()
|
||||
self.assertEqual(self.conn.status, STATUS_READY)
|
||||
# Now rollback and show that the new record is still there:
|
||||
self.conn.rollback()
|
||||
curs.execute('SELECT id, table1_id FROM table2 WHERE id = 2')
|
||||
self.assertEqual(curs.fetchall(), [(2, 1)])
|
||||
|
||||
def test_failed_commit(self):
|
||||
# Test that we can recover from a failed commit.
|
||||
# We use a deferred constraint to cause a failure on commit.
|
||||
curs = self.conn.cursor()
|
||||
curs.execute('SET CONSTRAINTS table2__table1_id__fk DEFERRED')
|
||||
curs.execute('INSERT INTO table2 VALUES (2, 42)')
|
||||
# The commit should fail, and move the cursor back to READY state
|
||||
self.assertEqual(self.conn.status, STATUS_BEGIN)
|
||||
self.assertRaises(psycopg2.OperationalError, self.conn.commit)
|
||||
self.assertEqual(self.conn.status, STATUS_READY)
|
||||
# The connection should be ready to use for the next transaction:
|
||||
curs.execute('SELECT 1')
|
||||
self.assertEqual(curs.fetchone()[0], 1)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user