Author: cito
Date: Tue May 8 05:18:10 2012
New Revision: 434
Log:
Add sqlstate attribute to DatabaseError instances.
Modified:
trunk/docs/changelog.txt
trunk/docs/pg.txt
trunk/module/TEST_PyGreSQL_classic.py
trunk/module/TEST_PyGreSQL_dbapi20.py
trunk/module/pg.py
trunk/module/pgdb.py
trunk/module/pgmodule.c
trunk/module/test_pg.py
Modified: trunk/docs/changelog.txt
==============================================================================
--- trunk/docs/changelog.txt Sat May 5 14:49:43 2012 (r433)
+++ trunk/docs/changelog.txt Tue May 8 05:18:10 2012 (r434)
@@ -9,6 +9,7 @@
(as suggested by Adam Frederick).
- Binary objects are now automatically escaped and unescaped.
- Bug in money quoting fixed. Amounts of $0.00 handled correctly.
+- All DatabaseError instances now have a sqlstate attribute.
Version 4.0 (2009-01-01)
------------------------
Modified: trunk/docs/pg.txt
==============================================================================
--- trunk/docs/pg.txt Sat May 5 14:49:43 2012 (r433)
+++ trunk/docs/pg.txt Tue May 8 05:18:10 2012 (r434)
@@ -74,7 +74,7 @@
:pgobject: If successful, the `pgobject` handling the connection
Exceptions raised:
- :TypeError: bad argument type, or too many arguments
+ :Type: bad argument type, or too many arguments
:SyntaxError: duplicate argument definition
:pg.InternalError: some error occurred during pg connection definition
@@ -448,6 +448,10 @@
method returns a `pgqueryobject` that can be accessed via the `getresult()`
or `dictresult()` method or simply printed. Otherwise, it returns `None`.
+ When the database could not process the query, a `pg.ProgrammingError` or
+ a `pg.InternalError` is raised. You can check the "SQLSTATE" code of this
+ error by reading its `sqlstate` attribute.
+
reset - resets the connection
-----------------------------
Syntax::
@@ -1103,7 +1107,7 @@
Exceptions raised:
:TypeError: too many parameters
- :pg.InternalError: invalid previous result
+ :MemoryError: internal memory error
Description:
This method returns the list of the values returned by the query.
@@ -1124,7 +1128,7 @@
Exceptions raised:
:TypeError: too many parameters
- :pg.InternalError: invalid previous result
+ :MemoryError: internal memory error
Description:
This method returns the list of the values returned by the query
@@ -1146,7 +1150,6 @@
Exceptions raised:
:TypeError: too many parameters
- :pg.InternalError: invalid previous result, or lost connection
Description:
This method returns the list of names of the fields defined for the
@@ -1167,7 +1170,6 @@
Exceptions raised:
:TypeError: invalid connection, bad parameter type, or too many parameters
:ValueError: invalid field number
- :pg.InternalError: invalid previous result, or lost connection
Description:
This method allows to find a field name from its rank number. It can be
@@ -1187,7 +1189,6 @@
Exceptions raised:
:TypeError: invalid connection, bad parameter type, or too many parameters
:ValueError: unknown field name
- :pg.InternalError: invalid previous result, or lost connection
Description:
This method returns a field number from its name. It can be used to
@@ -1225,7 +1226,7 @@
Dereferencing the initial `pgobject` is not a problem since Python won't
deallocate it before the `pglarge` object dereference it.
All functions return a generic error message on call error, whatever the
-exact error was. The `error` attribute of the object allow to get the exact
+exact error was. The `error` attribute of the object allows to get the exact
error message.
See also the PostgreSQL programmer's guide for more information about the
@@ -1408,7 +1409,7 @@
Exceptions raised:
:TypeError: invalid connection or invalid object,
bad parameter type, or too many parameters
- :IOError: object is not closed, or export error
+ :IOError: object is not closed, or export error
Description:
This methods allows to dump the content of a large object in a very simple
Modified: trunk/module/TEST_PyGreSQL_classic.py
==============================================================================
--- trunk/module/TEST_PyGreSQL_classic.py Sat May 5 14:49:43 2012
(r433)
+++ trunk/module/TEST_PyGreSQL_classic.py Tue May 8 05:18:10 2012
(r434)
@@ -21,10 +21,11 @@
db.query("SET DEFAULT_WITH_OIDS=TRUE")
db.query("SET STANDARD_CONFORMING_STRINGS=FALSE")
-class utility_test(unittest.TestCase):
+
+class UtilityTest(unittest.TestCase):
def setUp(self):
- # create test tables if they don't exist
+ """Setup test tables or empty them if they already exist."""
for t in ('_test1', '_test2'):
try:
db.query("CREATE SCHEMA " + t)
@@ -34,12 +35,12 @@
db.query("CREATE TABLE %s._test_schema "
"(%s int PRIMARY KEY)" % (t, t))
except Exception:
- pass
+ db.query("DELETE FROM %s._test_schema" % t)
try:
db.query("CREATE TABLE _test_schema "
"(_test int PRIMARY KEY, _i interval, dvar int DEFAULT 999)")
except Exception:
- pass
+ db.query("DELETE FROM _test_schema")
try:
db.query("CREATE VIEW _test_vschema AS "
"SELECT _test, 'abc'::text AS _test2 FROM _test_schema")
@@ -48,28 +49,23 @@
def test_invalidname(self):
"""Make sure that invalid table names are caught"""
-
self.failUnlessRaises(ProgrammingError, db.get_attnames, 'x.y.z')
def test_schema(self):
"""Does it differentiate the same table name in different schemas"""
-
# see if they differentiate the table names properly
self.assertEqual(
db.get_attnames('_test_schema'),
{'_test': 'int', 'oid': 'int', '_i': 'date', 'dvar': 'int'}
)
-
self.assertEqual(
db.get_attnames('public._test_schema'),
{'_test': 'int', 'oid': 'int', '_i': 'date', 'dvar': 'int'}
)
-
self.assertEqual(
db.get_attnames('_test1._test_schema'),
{'_test1': 'int', 'oid': 'int'}
)
-
self.assertEqual(
db.get_attnames('_test2._test_schema'),
{'_test2': 'int', 'oid': 'int'}
@@ -87,41 +83,39 @@
self.assertEqual(db.pkey('public.test1'), 'a')
def test_get(self):
- try:
- db.query("INSERT INTO _test_schema VALUES (1234)")
- except Exception:
- pass # OK if it already exists
-
+ db.query("INSERT INTO _test_schema VALUES (1234)")
db.get('_test_schema', 1234)
- db.get('_test_schema', 1234, keyname = '_test')
+ db.get('_test_schema', 1234, keyname='_test')
self.failUnlessRaises(ProgrammingError, db.get, '_test_vschema', 1234)
- db.get('_test_vschema', 1234, keyname = '_test')
+ db.get('_test_vschema', 1234, keyname='_test')
def test_insert(self):
- db.query("DELETE FROM _test_schema")
-
- d = dict(_test = 1234)
+ d = dict(_test=1234)
db.insert('_test_schema', d)
self.assertEqual(d['dvar'], 999)
-
- db.insert('_test_schema', _test = 1235)
+ db.insert('_test_schema', _test=1235)
self.assertEqual(d['dvar'], 999)
+ def test_sqlstate(self):
+ db.query("INSERT INTO _test_schema VALUES (1234)")
+ try:
+ db.query("INSERT INTO _test_schema VALUES (1234)")
+ except DatabaseError, error:
+ # currently PyGreSQL does not support IntegrityError
+ self.assert_(isinstance(error, ProgrammingError))
+ # the SQLSTATE error code for unique violation is 23505
+ self.assertEqual(error.sqlstate, '23505')
+
def test_mixed_case(self):
try:
db.query('CREATE TABLE _test_mc ("_Test" int PRIMARY KEY)')
except Exception:
- pass
-
- db.query("DELETE FROM _test_mc")
- d = dict(_Test = 1234)
+ db.query("DELETE FROM _test_mc")
+ d = dict(_Test=1234)
db.insert('_test_mc', d)
def test_update(self):
- try:
- db.query("INSERT INTO _test_schema VALUES (1234)")
- except Exception:
- pass # OK if it already exists
+ db.query("INSERT INTO _test_schema VALUES (1234)")
r = db.get('_test_schema', 1234)
r['dvar'] = 123
@@ -130,54 +124,54 @@
self.assertEqual(r['dvar'], 123)
r = db.get('_test_schema', 1234)
- db.update('_test_schema', _test = 1234, dvar = 456)
+ db.update('_test_schema', _test=1234, dvar=456)
r = db.get('_test_schema', 1234)
self.assertEqual(r['dvar'], 456)
r = db.get('_test_schema', 1234)
- db.update('_test_schema', r, dvar = 456)
+ db.update('_test_schema', r, dvar=456)
r = db.get('_test_schema', 1234)
self.assertEqual(r['dvar'], 456)
def test_quote(self):
- _quote = db._quote
- self.assertEqual(_quote(0, 'int'), "0")
- self.assertEqual(_quote(0, 'num'), "0")
- self.assertEqual(_quote('0', 'int'), "0")
- self.assertEqual(_quote('0', 'num'), "0")
- self.assertEqual(_quote(1, 'int'), "1")
- self.assertEqual(_quote(1, 'text'), "'1'")
- self.assertEqual(_quote(1, 'num'), "1")
- self.assertEqual(_quote('1', 'int'), "1")
- self.assertEqual(_quote('1', 'text'), "'1'")
- self.assertEqual(_quote('1', 'num'), "1")
- self.assertEqual(_quote(None, 'int'), "NULL")
- self.assertEqual(_quote(1, 'money'), "'1.00'")
- self.assertEqual(_quote('1', 'money'), "'1.00'")
- self.assertEqual(_quote(1.234, 'money'), "'1.23'")
- self.assertEqual(_quote('1.234', 'money'), "'1.23'")
- self.assertEqual(_quote(0, 'money'), "'0.00'")
- self.assertEqual(_quote(0.00, 'money'), "'0.00'")
- self.assertEqual(_quote(Decimal('0.00'), 'money'), "'0.00'")
- self.assertEqual(_quote(None, 'money'), "NULL")
- self.assertEqual(_quote('', 'money'), "NULL")
- self.assertEqual(_quote(0, 'bool'), "'f'")
- self.assertEqual(_quote('', 'bool'), "NULL")
- self.assertEqual(_quote('f', 'bool'), "'f'")
- self.assertEqual(_quote('off', 'bool'), "'f'")
- self.assertEqual(_quote('no', 'bool'), "'f'")
- self.assertEqual(_quote(1, 'bool'), "'t'")
- self.assertEqual(_quote('1', 'bool'), "'t'")
- self.assertEqual(_quote('t', 'bool'), "'t'")
- self.assertEqual(_quote('on', 'bool'), "'t'")
- self.assertEqual(_quote('yes', 'bool'), "'t'")
- self.assertEqual(_quote('true', 'bool'), "'t'")
- self.assertEqual(_quote('y', 'bool'), "'t'")
- self.assertEqual(_quote('', 'date'), "NULL")
- self.assertEqual(_quote('date', 'date'), "'date'")
- self.assertEqual(_quote('', 'text'), "''")
- self.assertEqual(_quote("'", 'text'), "''''")
- self.assertEqual(_quote("\\", 'text'), "'\\\\'")
+ q = db._quote
+ self.assertEqual(q(0, 'int'), "0")
+ self.assertEqual(q(0, 'num'), "0")
+ self.assertEqual(q('0', 'int'), "0")
+ self.assertEqual(q('0', 'num'), "0")
+ self.assertEqual(q(1, 'int'), "1")
+ self.assertEqual(q(1, 'text'), "'1'")
+ self.assertEqual(q(1, 'num'), "1")
+ self.assertEqual(q('1', 'int'), "1")
+ self.assertEqual(q('1', 'text'), "'1'")
+ self.assertEqual(q('1', 'num'), "1")
+ self.assertEqual(q(None, 'int'), "NULL")
+ self.assertEqual(q(1, 'money'), "'1.00'")
+ self.assertEqual(q('1', 'money'), "'1.00'")
+ self.assertEqual(q(1.234, 'money'), "'1.23'")
+ self.assertEqual(q('1.234', 'money'), "'1.23'")
+ self.assertEqual(q(0, 'money'), "'0.00'")
+ self.assertEqual(q(0.00, 'money'), "'0.00'")
+ self.assertEqual(q(Decimal('0.00'), 'money'), "'0.00'")
+ self.assertEqual(q(None, 'money'), "NULL")
+ self.assertEqual(q('', 'money'), "NULL")
+ self.assertEqual(q(0, 'bool'), "'f'")
+ self.assertEqual(q('', 'bool'), "NULL")
+ self.assertEqual(q('f', 'bool'), "'f'")
+ self.assertEqual(q('off', 'bool'), "'f'")
+ self.assertEqual(q('no', 'bool'), "'f'")
+ self.assertEqual(q(1, 'bool'), "'t'")
+ self.assertEqual(q('1', 'bool'), "'t'")
+ self.assertEqual(q('t', 'bool'), "'t'")
+ self.assertEqual(q('on', 'bool'), "'t'")
+ self.assertEqual(q('yes', 'bool'), "'t'")
+ self.assertEqual(q('true', 'bool'), "'t'")
+ self.assertEqual(q('y', 'bool'), "'t'")
+ self.assertEqual(q('', 'date'), "NULL")
+ self.assertEqual(q('date', 'date'), "'date'")
+ self.assertEqual(q('', 'text'), "''")
+ self.assertEqual(q("'", 'text'), "''''")
+ self.assertEqual(q("\\", 'text'), "'\\\\'")
if __name__ == '__main__':
Modified: trunk/module/TEST_PyGreSQL_dbapi20.py
==============================================================================
--- trunk/module/TEST_PyGreSQL_dbapi20.py Sat May 5 14:49:43 2012
(r433)
+++ trunk/module/TEST_PyGreSQL_dbapi20.py Tue May 8 05:18:10 2012
(r434)
@@ -56,7 +56,7 @@
con = self._connect()
curs = con.cursor()
curs.execute("select 1 union select 2 union select 3")
- self.assertEqual([r[0] for r in curs], [1,2,3])
+ self.assertEqual([r[0] for r in curs], [1, 2, 3])
def test_fetch_2_rows(self):
Decimal = pgdb.decimal_type()
@@ -96,6 +96,16 @@
finally:
con.close()
+ def test_sqlstate(self):
+ con = self._connect()
+ cur = con.cursor()
+ try:
+ cur.execute("select 1/0")
+ except pgdb.DatabaseError, error:
+ self.assert_(isinstance(error, pgdb.ProgrammingError))
+ # the SQLSTATE error code for division by zero is 22012
+ self.assertEqual(error.sqlstate, '22012')
+
def test_float(self):
from math import pi, e
try:
Modified: trunk/module/pg.py
==============================================================================
--- trunk/module/pg.py Sat May 5 14:49:43 2012 (r433)
+++ trunk/module/pg.py Tue May 8 05:18:10 2012 (r434)
@@ -90,6 +90,20 @@
"""Build oid key from qualified class name."""
return 'oid(%s)' % qcl
+def _db_error(msg, cls=DatabaseError):
+ """Returns DatabaseError with empty sqlstate attribute."""
+ error = cls(msg)
+ error.sqlstate = None
+ return error
+
+def _int_error(msg):
+ """Returns InternalError."""
+ return _db_error(msg, InternalError)
+
+def _prg_error(msg):
+ """Returns ProgrammingError."""
+ return _db_error(msg, ProgrammingError)
+
# The PostGreSQL database connection interface:
@@ -139,7 +153,7 @@
if self.db:
return getattr(self.db, name)
else:
- raise InternalError('Connection is not valid')
+ raise _int_error('Connection is not valid')
# Auxiliary methods
@@ -222,7 +236,7 @@
if len(s) > 1: # name already qualfied?
# should be database.schema.table or schema.table
if len(s) > 3:
- raise ProgrammingError('Too many dots in class name %s' % cl)
+ raise _prg_error('Too many dots in class name %s' % cl)
schema, cl = s[-2:]
else:
cl = s[0]
@@ -266,7 +280,7 @@
self.db.close()
self.db = None
else:
- raise InternalError('Connection already closed')
+ raise _int_error('Connection already closed')
def reset(self):
"""Reset connection with current parameters.
@@ -308,7 +322,7 @@
"""
# Wraps shared library function for debugging.
if not self.db:
- raise InternalError('Connection is not valid')
+ raise _int_error('Connection is not valid')
self._do_debug(qstr)
return self.db.query(qstr)
@@ -407,15 +421,14 @@
self._attnames = newattnames
return
elif newattnames:
- raise ProgrammingError(
- 'If supplied, newattnames must be a dictionary')
+ raise _prg_error('If supplied, newattnames must be a dictionary')
cl = self._split_schema(cl) # split into schema and class
qcl = _join_parts(cl) # build fully qualified name
# May as well cache them:
if qcl in self._attnames:
return self._attnames[qcl]
if qcl not in self.get_relations('rv'):
- raise ProgrammingError('Class %s does not exist' % qcl)
+ raise _prg_error('Class %s does not exist' % qcl)
t = {}
for att, typ in self.db.query("SELECT pg_attribute.attname,"
" pg_type.typname FROM pg_class"
@@ -487,14 +500,14 @@
if not keyname:
# use the primary key by default
try:
- keyname = self.pkey(qcl)
+ keyname = self.pkey(qcl)
except KeyError:
- raise ProgrammingError('Class %s has no primary key' % qcl)
+ raise _prg_error('Class %s has no primary key' % qcl)
# We want the oid for later updates if that isn't the key
if keyname == 'oid':
if isinstance(arg, dict):
if qoid not in arg:
- raise ProgrammingError('%s not in arg' % qoid)
+ raise _db_error('%s not in arg' % qoid)
else:
arg = {qoid: arg}
where = 'oid = %s' % arg[qoid]
@@ -505,7 +518,7 @@
keyname = (keyname,)
if not isinstance(arg, dict):
if len(keyname) > 1:
- raise ProgrammingError('Composite key needs dict as arg')
+ raise _prg_error('Composite key needs dict as arg')
arg = dict([(k, arg) for k in keyname])
where = ' AND '.join(['%s = %s'
% (k, self._quote(arg[k], attnames[k])) for k in keyname])
@@ -514,7 +527,7 @@
self._do_debug(q)
res = self.db.query(q).dictresult()
if not res:
- raise DatabaseError('No such record in %s where %s' % (qcl, where))
+ raise _db_error('No such record in %s where %s' % (qcl, where))
for att, value in res[0].iteritems():
arg[att == 'oid' and qoid or att] = value
return arg
@@ -600,14 +613,14 @@
try:
keyname = self.pkey(qcl)
except KeyError:
- raise ProgrammingError('Class %s has no primary key' % qcl)
+ raise _prg_error('Class %s has no primary key' % qcl)
if isinstance(keyname, basestring):
keyname = (keyname,)
try:
where = ' AND '.join(['%s = %s'
% (k, self._quote(d[k], attnames[k])) for k in keyname])
except KeyError:
- raise ProgrammingError('Update needs primary key or oid.')
+ raise _prg_error('Update needs primary key or oid.')
values = []
for n in attnames:
if n in d and n not in keyname:
@@ -688,7 +701,7 @@
try:
keyname = self.pkey(qcl)
except KeyError:
- raise ProgrammingError('Class %s has no primary key' % qcl)
+ raise _prg_error('Class %s has no primary key' % qcl)
if isinstance(keyname, basestring):
keyname = (keyname,)
attnames = self.get_attnames(qcl)
@@ -696,7 +709,7 @@
where = ' AND '.join(['%s = %s'
% (k, self._quote(d[k], attnames[k])) for k in keyname])
except KeyError:
- raise ProgrammingError('Delete needs primary key or oid.')
+ raise _prg_error('Delete needs primary key or oid.')
q = 'DELETE FROM %s WHERE %s' % (qcl, where)
self._do_debug(q)
return int(self.db.query(q))
Modified: trunk/module/pgdb.py
==============================================================================
--- trunk/module/pgdb.py Sat May 5 14:49:43 2012 (r433)
+++ trunk/module/pgdb.py Tue May 8 05:18:10 2012 (r434)
@@ -145,6 +145,18 @@
'numeric': Decimal, 'money': _cast_money}
+def _db_error(msg, cls=DatabaseError):
+ """Returns DatabaseError with empty sqlstate attribute."""
+ error = cls(msg)
+ error.sqlstate = None
+ return error
+
+
+def _op_error(msg):
+ """Returns OperationalError."""
+ return _db_error(msg, OperationalError)
+
+
class pgdbTypeCache(dict):
"""Cache for database types."""
@@ -219,7 +231,7 @@
if isinstance(val, (datetime, date, time, timedelta)):
val = str(val)
elif isinstance(val, unicode):
- val = val.encode( 'utf8' )
+ val = val.encode('utf8')
if isinstance(val, str):
if isinstance(val, Binary):
val = self._cnx.escape_bytea(val)
@@ -311,8 +323,10 @@
if not self._dbcnx._tnx:
try:
self._cnx.source().execute(sql)
+ except DatabaseError:
+ raise
except Exception:
- raise OperationalError("can't start transaction")
+ raise _op_error("can't start transaction")
self._dbcnx._tnx = True
for params in param_seq:
if params:
@@ -324,12 +338,12 @@
totrows += rows
else:
self.rowcount = -1
- except Error:
- msg = sys.exc_info()[1]
- raise DatabaseError("error '%s' in '%s'" % (msg, sql))
- except Exception:
- err = sys.exc_info()[1]
- raise OperationalError("internal error in '%s': %s" % (sql, err))
+ except DatabaseError:
+ raise
+ except Error, err:
+ raise _db_error("error '%s' in '%s'" % (err, sql))
+ except Exception, err:
+ raise _op_error("internal error in '%s': %s" % (sql, err))
# then initialize result raw count and description
if self._src.resulttype == RESULT_DQL:
self.rowcount = self._src.ntuples
@@ -372,9 +386,10 @@
self.arraysize = size
try:
result = self._src.fetch(size)
- except Error:
- err = sys.exc_info()[1]
- raise DatabaseError(str(err))
+ except DatabaseError:
+ raise
+ except Error, err:
+ raise _db_error(str(err))
row_factory = self.row_factory
typecast = self._type_cache.typecast
coltypes = [desc[1] for desc in self.description]
@@ -429,7 +444,7 @@
try:
self._cnx.source()
except Exception:
- raise OperationalError("invalid connection")
+ raise _op_error("invalid connection")
def close(self):
"""Close the connection object."""
@@ -437,7 +452,7 @@
self._cnx.close()
self._cnx = None
else:
- raise OperationalError("connection has been closed")
+ raise _op_error("connection has been closed")
def commit(self):
"""Commit any pending transaction to the database."""
@@ -446,10 +461,12 @@
self._tnx = False
try:
self._cnx.source().execute("COMMIT")
+ except DatabaseError:
+ raise
except Exception:
- raise OperationalError("can't commit")
+ raise _op_error("can't commit")
else:
- raise OperationalError("connection has been closed")
+ raise _op_error("connection has been closed")
def rollback(self):
"""Roll back to the start of any pending transaction."""
@@ -458,10 +475,12 @@
self._tnx = False
try:
self._cnx.source().execute("ROLLBACK")
+ except DatabaseError:
+ raise
except Exception:
- raise OperationalError("can't rollback")
+ raise _op_error("can't rollback")
else:
- raise OperationalError("connection has been closed")
+ raise _op_error("connection has been closed")
def cursor(self):
"""Return a new Cursor Object using the connection."""
@@ -469,9 +488,9 @@
try:
return pgdbCursor(self)
except Exception:
- raise OperationalError("invalid connection")
+ raise _op_error("invalid connection")
else:
- raise OperationalError("connection has been closed")
+ raise _op_error("connection has been closed")
### Module Interface
Modified: trunk/module/pgmodule.c
==============================================================================
--- trunk/module/pgmodule.c Sat May 5 14:49:43 2012 (r433)
+++ trunk/module/pgmodule.c Tue May 8 05:18:10 2012 (r434)
@@ -217,6 +217,44 @@
/* --------------------------------------------------------------------- */
/* INTERNAL FUNCTIONS */
+/* sets database error with sqlstate attribute */
+/* This should be used when raising a subclass of DatabaseError */
+static void
+set_dberror(PyObject *type, const char *msg, PGresult *result)
+{
+ PyObject *err = NULL;
+ PyObject *str;
+
+ str = PyString_FromString(msg);
+ if (str)
+ {
+ err = PyObject_CallFunctionObjArgs(type, str, NULL);
+ Py_DECREF(str);
+ }
+ else
+ err = NULL;
+ if (err)
+ {
+ if (result) {
+ char *sqlstate = PQresultErrorField(result,
PG_DIAG_SQLSTATE);
+ str = sqlstate ? PyString_FromStringAndSize(sqlstate,
5) : NULL;
+ }
+ else
+ str = NULL;
+ if (!str)
+ {
+ Py_INCREF(Py_None);
+ str = Py_None;
+ }
+ PyObject_SetAttrString(err, "sqlstate", str);
+ Py_DECREF(str);
+ PyErr_SetObject(type, err);
+ Py_DECREF(err);
+ }
+ else
+ PyErr_SetString(type, msg);
+}
+
/* checks connection validity */
static int
@@ -224,7 +262,7 @@
{
if (!self->valid)
{
- PyErr_SetString(IntegrityError, "connection has been closed.");
+ set_dberror(OperationalError, "connection has been closed.",
NULL);
return 0;
}
return 1;
@@ -240,7 +278,7 @@
if (!self->lo_oid)
{
- PyErr_SetString(IntegrityError, "object is not valid (null
oid).");
+ set_dberror(IntegrityError, "object is not valid (null oid).",
NULL);
return 0;
}
@@ -272,19 +310,20 @@
{
if (!self->valid)
{
- PyErr_SetString(IntegrityError, "object has been closed");
+ set_dberror(OperationalError, "object has been closed", NULL);
return 0;
}
if ((level & CHECK_RESULT) && self->result == NULL)
{
- PyErr_SetString(DatabaseError, "no result.");
+ set_dberror(DatabaseError, "no result.", NULL);
return 0;
}
if ((level & CHECK_DQL) && self->result_type != RESULT_DQL)
{
- PyErr_SetString(DatabaseError, "last query did not return
tuples.");
+ set_dberror(DatabaseError,
+ "last query did not return tuples.", self->result);
return 0;
}
@@ -662,11 +701,12 @@
case PGRES_BAD_RESPONSE:
case PGRES_FATAL_ERROR:
case PGRES_NONFATAL_ERROR:
- PyErr_SetString(ProgrammingError,
PQerrorMessage(self->pgcnx->cnx));
+ set_dberror(ProgrammingError,
+ PQerrorMessage(self->pgcnx->cnx), self->result);
break;
default:
- PyErr_SetString(InternalError, "internal error: "
- "unknown result status.");
+ set_dberror(InternalError, "internal error: "
+ "unknown result status.", self->result);
break;
}
@@ -1278,7 +1318,7 @@
/* gets arguments */
if (!PyArg_ParseTuple(args, "i", &size))
{
- PyErr_SetString(PyExc_TypeError, "read(size), wih size
(integer).");
+ PyErr_SetString(PyExc_TypeError, "read(size), with size
(integer).");
return NULL;
}
@@ -1716,7 +1756,7 @@
if (PQstatus(npgobj->cnx) == CONNECTION_BAD)
{
- PyErr_SetString(InternalError, PQerrorMessage(npgobj->cnx));
+ set_dberror(InternalError, PQerrorMessage(npgobj->cnx), NULL);
Py_XDECREF(npgobj);
return NULL;
}
@@ -1787,7 +1827,7 @@
/* connection object cannot already be closed */
if (!self->cnx)
{
- PyErr_SetString(InternalError, "Connection already closed");
+ set_dberror(InternalError, "Connection already closed", NULL);
return NULL;
}
@@ -2428,7 +2468,8 @@
case PGRES_BAD_RESPONSE:
case PGRES_FATAL_ERROR:
case PGRES_NONFATAL_ERROR:
- PyErr_SetString(ProgrammingError,
PQerrorMessage(self->cnx));
+ set_dberror(ProgrammingError,
+ PQerrorMessage(self->cnx), result);
break;
case PGRES_COMMAND_OK:
{
/* INSERT, UPDATE, DELETE */
@@ -2455,8 +2496,8 @@
Py_INCREF(Py_None);
return Py_None;
default:
- PyErr_SetString(InternalError, "internal error:
"
- "unknown result status.");
+ set_dberror(InternalError,
+ "internal error: unknown result
status.", result);
break;
}
@@ -2997,7 +3038,7 @@
lo_oid = lo_creat(self->cnx, mode);
if (lo_oid == 0)
{
- PyErr_SetString(OperationalError, "can't create large object.");
+ set_dberror(OperationalError, "can't create large object.",
NULL);
return NULL;
}
@@ -3059,7 +3100,7 @@
lo_oid = lo_import(self->cnx, name);
if (lo_oid == 0)
{
- PyErr_SetString(OperationalError, "can't create large object.");
+ set_dberror(OperationalError, "can't create large object.",
NULL);
return NULL;
}
Modified: trunk/module/test_pg.py
==============================================================================
--- trunk/module/test_pg.py Sat May 5 14:49:43 2012 (r433)
+++ trunk/module/test_pg.py Tue May 8 05:18:10 2012 (r434)
@@ -909,6 +909,12 @@
def testMethodQuery(self):
self.db.query("select 1+1")
+ def testMethodQueryProgrammingError(self):
+ try:
+ self.db.query("select 1/0")
+ except pg.ProgrammingError, error:
+ self.assertEqual(error.sqlstate, '22012')
+
def testMethodEndcopy(self):
try:
self.db.endcopy()
@@ -1123,6 +1129,12 @@
self.assert_(isinstance(r, str))
self.assertEqual(r, '5')
+ def testQueryProgrammingError(self):
+ try:
+ self.db.query("select 1/0")
+ except pg.ProgrammingError, error:
+ self.assertEqual(error.sqlstate, '22012')
+
def testPkey(self):
smart_ddl(self.db, "drop table pkeytest0")
smart_ddl(self.db, "create table pkeytest0 ("
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo.cgi/pygresql