Fixed bug #194 (and added nice MD project not that C/C++ is supported.)

This commit is contained in:
Federico Di Gregorio 2007-09-08 08:54:30 +00:00
parent 0422506404
commit c9e701baa9
5 changed files with 137 additions and 4 deletions

View File

@ -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> 2007-09-01 Federico Di Gregorio <fog@initd.org>
* Added "name" parameter to all .cursor() calls in extras.py. * Added "name" parameter to all .cursor() calls in extras.py.

View File

@ -40,8 +40,10 @@ conn_notice_callback(void *args, const char *message)
Dprintf("conn_notice_callback: %s", message); Dprintf("conn_notice_callback: %s", message);
/* unfortunately the old protocl return COPY FROM errors only as notices, /* unfortunately the old protocl return COPY FROM errors only as notices,
so we need to filter them looking for such errors */ so we need to filter them looking for such errors (but we do it
if (strncmp(message, "ERROR", 5) == 0) 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); pq_set_critical(self, message);
else else
PyList_Append(self->notice_list, PyString_FromString(message)); PyList_Append(self->notice_list, PyString_FromString(message));
@ -208,6 +210,8 @@ conn_commit(connectionObject *self)
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
if (res == -1)
pq_resolve_critical(self, 0);
return res; return res;
} }
@ -227,6 +231,8 @@ conn_rollback(connectionObject *self)
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
if (res == -1)
pq_resolve_critical(self, 0);
return res; return res;
} }

View File

@ -148,10 +148,27 @@ pq_set_critical(connectionObject *conn, const char *msg)
if (msg == NULL) if (msg == NULL)
msg = PQerrorMessage(conn->pgconn); msg = PQerrorMessage(conn->pgconn);
if (conn->critical) free(conn->critical); if (conn->critical) free(conn->critical);
Dprintf("pq_set_critical: setting %s", msg);
if (msg && msg[0] != '\0') conn->critical = strdup(msg); if (msg && msg[0] != '\0') conn->critical = strdup(msg);
else conn->critical = NULL; 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 * PyObject *
pq_resolve_critical(connectionObject *conn, int close) 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 */ /* we don't want to destroy this connection but just close it */
if (close == 1) conn_close(conn); if (close == 1) conn_close(conn);
/* remember to clear the critical! */
pq_clear_critical(conn);
} }
return NULL; return NULL;
} }
@ -268,6 +288,9 @@ pq_commit(connectionObject *conn)
pgstatus = PQresultStatus(pgres); pgstatus = PQresultStatus(pgres);
if (pgstatus != PGRES_COMMAND_OK ) { if (pgstatus != PGRES_COMMAND_OK ) {
Dprintf("pq_commit: result is NOT 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); pq_set_critical(conn, NULL);
goto cleanup; goto cleanup;
} }
@ -400,8 +423,7 @@ pq_execute(cursorObject *curs, const char *query, int async)
if (pq_begin(curs->conn) < 0) { if (pq_begin(curs->conn) < 0) {
pthread_mutex_unlock(&(curs->conn->lock)); pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS; Py_BLOCK_THREADS;
PyErr_SetString(OperationalError, pq_resolve_critical(curs->conn, 0);
PQerrorMessage(curs->conn->pgconn));
return -1; return -1;
} }

21
tests/__init__.py Normal file
View 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
View 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()