mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-26 02:43:43 +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>
|
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.
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
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