Changeset 823


Ignore:
Timestamp:
Feb 5, 2016, 4:35:34 PM (4 years ago)
Author:
cito
Message:

Raise the proper subclasses of DatabaseError?

Particularly, we raise IntegrityError? instead of ProgrammingError? for
duplicate keys. This also makes PyGreSQL more useable with SQLAlchemy.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/docs/contents/changelog.rst

    r821 r823  
    9494    - A new type helper Interval() has been added.
    9595- Changes concerning both modules:
     96    - PyGreSQL now tries to raise more specific and appropriate subclasses of
     97      DatabaseError than just ProgrammingError. Particularly, when database
     98      constraints are violated, it raises an IntegrityError now.
    9699    - The modules now provide get_typecast() and set_typecast() methods
    97100      allowing to control the typecasting on the global level.  The connection
  • trunk/pgdb.py

    r822 r823  
    865865                except DatabaseError:
    866866                    raise  # database provides error message
    867                 except Exception as err:
     867                except Exception:
    868868                    raise _op_error("Can't start transaction")
    869869                self._dbcnx._tnx = True
  • trunk/pgmodule.c

    r817 r823  
    12971297}
    12981298
     1299/* gets appropriate error type from sqlstate */
     1300static PyObject *
     1301get_error_type(const char *sqlstate)
     1302{
     1303        switch (sqlstate[0]) {
     1304                case '0':
     1305                        switch (sqlstate[1])
     1306                        {
     1307                                case 'A':
     1308                                        return NotSupportedError;
     1309                        }
     1310                        break;
     1311                case '2':
     1312                        switch (sqlstate[1])
     1313                        {
     1314                                case '0':
     1315                                case '1':
     1316                                        return ProgrammingError;
     1317                                case '2':
     1318                                        return DataError;
     1319                                case '3':
     1320                                        return IntegrityError;
     1321                                case '4':
     1322                                case '5':
     1323                                        return InternalError;
     1324                                case '6':
     1325                                case '7':
     1326                                case '8':
     1327                                        return OperationalError;
     1328                                case 'B':
     1329                                case 'D':
     1330                                case 'F':
     1331                                        return InternalError;
     1332                        }
     1333                        break;
     1334                case '3':
     1335                        switch (sqlstate[1])
     1336                        {
     1337                                case '4':
     1338                                        return OperationalError;
     1339                                case '8':
     1340                                case '9':
     1341                                case 'B':
     1342                                        return InternalError;
     1343                                case 'D':
     1344                                case 'F':
     1345                                        return ProgrammingError;
     1346                        }
     1347                        break;
     1348                case '4':
     1349                        switch (sqlstate[1])
     1350                        {
     1351                                case '0':
     1352                                        return OperationalError;
     1353                                case '2':
     1354                                case '4':
     1355                                        return ProgrammingError;
     1356                        }
     1357                        break;
     1358                case '5':
     1359                case 'H':
     1360                        return OperationalError;
     1361                case 'F':
     1362                case 'P':
     1363                case 'X':
     1364                        return InternalError;
     1365        }
     1366        return DatabaseError;
     1367}
     1368
    12991369/* sets database error with sqlstate attribute */
    13001370/* This should be used when raising a subclass of DatabaseError */
     
    13021372set_dberror(PyObject *type, const char *msg, PGresult *result)
    13031373{
    1304         PyObject *err = NULL;
    1305         PyObject *str;
    1306 
    1307         if (!(str = PyStr_FromString(msg)))
    1308                 err = NULL;
     1374        PyObject   *err_obj, *msg_obj, *sql_obj = NULL;
     1375
     1376        if (result)
     1377        {
     1378                char *sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE);
     1379                if (sqlstate)
     1380                {
     1381                        sql_obj = PyStr_FromStringAndSize(sqlstate, 5);
     1382                        type = get_error_type(sqlstate);
     1383                }
     1384        }
     1385        if (!sql_obj)
     1386        {
     1387                Py_INCREF(Py_None);
     1388                sql_obj = Py_None;
     1389        }
     1390        msg_obj = PyStr_FromString(msg);
     1391        err_obj = PyObject_CallFunctionObjArgs(type, msg_obj, NULL);
     1392        if (err_obj)
     1393        {
     1394                Py_DECREF(msg_obj);
     1395                PyObject_SetAttrString(err_obj, "sqlstate", sql_obj);
     1396                Py_DECREF(sql_obj);
     1397                PyErr_SetObject(type, err_obj);
     1398                Py_DECREF(err_obj);
     1399        }
    13091400        else
    13101401        {
    1311                 err = PyObject_CallFunctionObjArgs(type, str, NULL);
    1312                 Py_DECREF(str);
    1313         }
    1314         if (err)
    1315         {
    1316                 if (result)
    1317                 {
    1318                         char *sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE);
    1319                         str = sqlstate ? PyStr_FromStringAndSize(sqlstate, 5) : NULL;
    1320                 }
    1321                 else
    1322                         str = NULL;
    1323                 if (!str)
    1324                 {
    1325                         Py_INCREF(Py_None);
    1326                         str = Py_None;
    1327                 }
    1328                 PyObject_SetAttrString(err, "sqlstate", str);
    1329                 Py_DECREF(str);
    1330                 PyErr_SetObject(type, err);
    1331                 Py_DECREF(err);
    1332         }
    1333         else
    13341402                PyErr_SetString(type, msg);
     1403        }
    13351404}
    13361405
  • trunk/tests/test_classic.py

    r762 r823  
    3737    db.query("SET DEFAULT_WITH_OIDS=FALSE")
    3838    db.query("SET STANDARD_CONFORMING_STRINGS=FALSE")
     39    db.query("SET CLIENT_MIN_MESSAGES=WARNING")
    3940    return db
    4041
     
    8384        """Make sure that invalid table names are caught"""
    8485        db = opendb()
    85         self.assertRaises(ProgrammingError, db.get_attnames, 'x.y.z')
     86        self.assertRaises(NotSupportedError, db.get_attnames, 'x.y.z')
    8687
    8788    def test_schema(self):
     
    149150                db.insert(t, d)
    150151                db.insert(t, d)
    151         except ProgrammingError:
     152        except IntegrityError:
    152153            pass
    153154        with db:
     
    168169            db.query("INSERT INTO _test_schema VALUES (1234)")
    169170        except DatabaseError as error:
    170             # currently PyGreSQL does not support IntegrityError
    171             self.assertTrue(isinstance(error, ProgrammingError))
     171            self.assertTrue(isinstance(error, IntegrityError))
    172172            # the SQLSTATE error code for unique violation is 23505
    173173            self.assertEqual(error.sqlstate, '23505')
  • trunk/tests/test_classic_connection.py

    r814 r823  
    232232            try:
    233233                self.connection.query('select pg_sleep(5)').getresult()
    234             except pg.ProgrammingError as error:
     234            except pg.DatabaseError as error:
    235235                errors.append(str(error))
    236236
     
    332332    def testSelectDotSemicolon(self):
    333333        q = "select .;"
    334         self.assertRaises(pg.ProgrammingError, self.c.query, q)
     334        self.assertRaises(pg.DatabaseError, self.c.query, q)
    335335
    336336    def testGetresult(self):
     
    604604        try:
    605605            v = self.c.query(q).getresult()[0][0]
    606         except pg.ProgrammingError:
     606        except(pg.DataError, pg.NotSupportedError):
    607607            self.skipTest("database does not support utf8")
    608608        self.assertIsInstance(v, str)
     
    621621        try:
    622622            v = self.c.query(q).dictresult()[0]['greeting']
    623         except pg.ProgrammingError:
     623        except (pg.DataError, pg.NotSupportedError):
    624624            self.skipTest("database does not support utf8")
    625625        self.assertIsInstance(v, str)
     
    633633        try:
    634634            self.c.query('set client_encoding=latin1')
    635         except pg.ProgrammingError:
     635        except (pg.DataError, pg.NotSupportedError):
    636636            self.skipTest("database does not support latin1")
    637637        result = u'Hello, wörld!'
     
    650650        try:
    651651            self.c.query('set client_encoding=latin1')
    652         except pg.ProgrammingError:
     652        except (pg.DataError, pg.NotSupportedError):
    653653            self.skipTest("database does not support latin1")
    654654        result = u'Hello, wörld!'
     
    667667        try:
    668668            self.c.query('set client_encoding=iso_8859_5')
    669         except pg.ProgrammingError:
     669        except (pg.DataError, pg.NotSupportedError):
    670670            self.skipTest("database does not support cyrillic")
    671671        result = u'Hello, ЌОр!'
     
    684684        try:
    685685            self.c.query('set client_encoding=iso_8859_5')
    686         except pg.ProgrammingError:
     686        except (pg.DataError, pg.NotSupportedError):
    687687            self.skipTest("database does not support cyrillic")
    688688        result = u'Hello, ЌОр!'
     
    701701        try:
    702702            self.c.query('set client_encoding=latin9')
    703         except pg.ProgrammingError:
     703        except (pg.DataError, pg.NotSupportedError):
    704704            self.skipTest("database does not support latin9")
    705705        result = u'smœrebrœd with praÅŸská Å¡unka (pay in ¢, £, €, or Â¥)'
     
    718718        try:
    719719            self.c.query('set client_encoding=latin9')
    720         except pg.ProgrammingError:
     720        except (pg.DataError, pg.NotSupportedError):
    721721            self.skipTest("database does not support latin9")
    722722        result = u'smœrebrœd with praÅŸská Å¡unka (pay in ¢, £, €, or Â¥)'
     
    819819        self.assertEqual(query("select $1::text union select $2::text",
    820820            ('Hello', 'world')).getresult(), [('Hello',), ('world',)])
     821        try:
     822            query("select 'wörld'")
     823        except (pg.DataError, pg.NotSupportedError):
     824            self.skipTest('database does not support utf8')
    821825        self.assertEqual(query("select $1||', '||$2||'!'", ('Hello',
    822826            'w\xc3\xb6rld')).getresult(), [('Hello, w\xc3\xb6rld!',)])
     
    827831            query('set client_encoding=utf8')
    828832            query("select 'wörld'").getresult()[0][0] == 'wörld'
    829         except pg.ProgrammingError:
     833        except (pg.DataError, pg.NotSupportedError):
    830834            self.skipTest("database does not support utf8")
    831835        self.assertEqual(query("select $1||', '||$2||'!'",
     
    837841            query('set client_encoding=latin1')
    838842            query("select 'wörld'").getresult()[0][0] == 'wörld'
    839         except pg.ProgrammingError:
     843        except (pg.DataError, pg.NotSupportedError):
    840844            self.skipTest("database does not support latin1")
    841845        r = query("select $1||', '||$2||'!'", ('Hello', u'wörld')).getresult()
     
    864868            query('set client_encoding=iso_8859_5')
    865869            query("select 'ЌОр'").getresult()[0][0] == 'ЌОр'
    866         except pg.ProgrammingError:
     870        except (pg.DataError, pg.NotSupportedError):
    867871            self.skipTest("database does not support cyrillic")
    868872        self.assertRaises(UnicodeError, query, "select $1||', '||$2||'!'",
     
    913917    def assert_proper_cast(self, value, pgtype, pytype):
    914918        q = 'select $1::%s' % (pgtype,)
    915         r = self.c.query(q, (value,)).getresult()[0][0]
     919        try:
     920            r = self.c.query(q, (value,)).getresult()[0][0]
     921        except pg.ProgrammingError:
     922            if pgtype in ('json', 'jsonb'):
     923                self.skipTest('database does not support json')
    916924        self.assertIsInstance(r, pytype)
    917925        if isinstance(value, str):
     
    9951003        # that it does not consider encoding when calculating lengths.
    9961004        c.query("set client_encoding=utf8")
    997         cls.has_encoding = c.query(
    998             "select length('À') - length('a')").getresult()[0][0] == 0
     1005        try:
     1006            c.query("select 'À'")
     1007        except (pg.DataError, pg.NotSupportedError):
     1008            cls.has_encoding = False
     1009        else:
     1010            cls.has_encoding = c.query(
     1011                "select length('À') - length('a')").getresult()[0][0] == 0
    9991012        c.close()
    10001013        cls.cls_set_up = True
     
    11231136        try:
    11241137            self.c.query("select '€', 'kÀse', 'сыр', 'pont-l''évêque'")
    1125         except pg.ProgrammingError:
     1138        except pg.DataError:
    11261139            self.skipTest("database does not support utf8")
    11271140        # non-ascii chars do not fit in char(1) when there is no encoding
     
    11411154        try:
    11421155            self.c.query("select '€', 'kÀse', 'сыр', 'pont-l''évêque'")
    1143         except pg.ProgrammingError:
     1156        except pg.DataError:
    11441157            self.skipTest("database does not support utf8")
    11451158        # non-ascii chars do not fit in char(1) when there is no encoding
     
    11611174            self.c.query("set client_encoding=latin1")
    11621175            self.c.query("select 'Â¥'")
    1163         except pg.ProgrammingError:
     1176        except (pg.DataError, pg.NotSupportedError):
    11641177            self.skipTest("database does not support latin1")
    11651178        # non-ascii chars do not fit in char(1) when there is no encoding
     
    11851198            self.c.query("set client_encoding=latin9")
    11861199            self.c.query("select '€'")
    1187         except pg.ProgrammingError:
     1200        except (pg.DataError, pg.NotSupportedError):
    11881201            self.skipTest("database does not support latin9")
    11891202            return
     
    12581271        putline = self.c.putline
    12591272        query = self.c.query
     1273        try:
     1274            query("select 'kÀse+wÃŒrstel'")
     1275        except (pg.DataError, pg.NotSupportedError):
     1276            self.skipTest('database does not support utf8')
    12601277        query("copy test from stdin")
    12611278        try:
     
    12931310        getline = self.c.getline
    12941311        query = self.c.query
     1312        try:
     1313            query("select 'kÀse+wÃŒrstel'")
     1314        except (pg.DataError, pg.NotSupportedError):
     1315            self.skipTest('database does not support utf8')
    12951316        data = [(54, u'kÀse'.encode('utf8')), (73, u'wÃŒrstel')]
    12961317        self.c.inserttable('test', data)
     
    14751496            try:
    14761497                query("set lc_monetary='%s'" % lc)
    1477             except pg.ProgrammingError:
     1498            except pg.DataError:
    14781499                pass
    14791500            else:
     
    14831504        try:
    14841505            r = query(select_money)
    1485         except pg.ProgrammingError:
     1506        except pg.DataError:
    14861507            # this can happen if the currency signs cannot be
    14871508            # converted using the encoding of the test database
     
    15301551            try:
    15311552                query("set lc_monetary='%s'" % lc)
    1532             except pg.ProgrammingError:
     1553            except pg.DataError:
    15331554                pass
    15341555            else:
     
    15391560        try:
    15401561            r = query(select_money)
    1541         except pg.ProgrammingError:
     1562        except pg.DataError:
    15421563            self.skipTest("database does not support English money")
    15431564        pg.set_decimal_point(None)
     
    16001621        try:
    16011622            r = query("select 3425::numeric")
    1602         except pg.ProgrammingError:
     1623        except pg.DatabaseError:
    16031624            self.skipTest('database does not support numeric')
    16041625        r = r.getresult()[0][0]
  • trunk/tests/test_classic_dbwrapper.py

    r821 r823  
    307307        self.assertRaises(ValueError, self.db.query, '')
    308308
    309     def testMethodQueryProgrammingError(self):
     309    def testMethodQueryDataError(self):
    310310        try:
    311311            self.db.query("select 1/0")
    312         except pg.ProgrammingError as error:
     312        except pg.DataError as error:
    313313            self.assertEqual(error.sqlstate, '22012')
    314314
     
    874874        self.assertRaises(ValueError, self.db.query, '')
    875875
    876     def testQueryProgrammingError(self):
     876    def testQueryDataError(self):
    877877        try:
    878878            self.db.query("select 1/0")
    879         except pg.ProgrammingError as error:
     879        except pg.DataError as error:
    880880            self.assertEqual(error.sqlstate, '22012')
    881881
     
    12131213        self.assertEqual(can('test', 'update'), True)
    12141214        self.assertEqual(can('test', 'delete'), True)
    1215         self.assertRaises(pg.ProgrammingError, can, 'test', 'foobar')
     1215        self.assertRaises(pg.DataError, can, 'test', 'foobar')
    12161216        self.assertRaises(pg.ProgrammingError, can, 'table_does_not_exist')
    12171217        r = self.db.query('select rolsuper FROM pg_roles'
     
    15841584        self.assertIsInstance(r, dict)
    15851585        self.assertEqual(r['n'], 7)
    1586         self.assertRaises(pg.ProgrammingError, insert, 'test_table', r)
     1586        self.assertRaises(pg.IntegrityError, insert, 'test_table', r)
    15871587        r['n'] = 6
    1588         self.assertRaises(pg.ProgrammingError, insert, 'test_table', r, n=7)
     1588        self.assertRaises(pg.IntegrityError, insert, 'test_table', r, n=7)
    15891589        self.assertIsInstance(r, dict)
    15901590        self.assertEqual(r['n'], 7)
     
    16341634        try:
    16351635            insert('test_view', r)
    1636         except pg.ProgrammingError as error:
     1636        except pg.NotSupportedError as error:
    16371637            if self.db.server_version < 90300:
    16381638                # must setup rules in older PostgreSQL versions
     
    22742274             " (select count(*) from test_child)")
    22752275        self.assertEqual(query(q).getresult()[0], (3, 3))
    2276         self.assertRaises(pg.ProgrammingError,
     2276        self.assertRaises(pg.IntegrityError,
    22772277                          delete, 'test_parent', None, n=2)
    2278         self.assertRaises(pg.ProgrammingError,
     2278        self.assertRaises(pg.IntegrityError,
    22792279                          delete, 'test_parent *', None, n=2)
    22802280        r = delete('test_child', None, n=2)
     
    22842284        self.assertEqual(r, 1)
    22852285        self.assertEqual(query(q).getresult()[0], (2, 2))
    2286         self.assertRaises(pg.ProgrammingError,
     2286        self.assertRaises(pg.IntegrityError,
    22872287                          delete, 'test_parent', dict(n=0))
    2288         self.assertRaises(pg.ProgrammingError,
     2288        self.assertRaises(pg.IntegrityError,
    22892289                          delete, 'test_parent *', dict(n=0))
    22902290        r = delete('test_child', dict(n=0))
     
    23732373        r = query(q).getresult()[0]
    23742374        self.assertEqual(r, (3, 3))
    2375         self.assertRaises(pg.ProgrammingError, truncate, 'test_parent')
     2375        self.assertRaises(pg.NotSupportedError, truncate, 'test_parent')
    23762376        truncate(['test_parent', 'test_child'])
    23772377        r = query(q).getresult()[0]
     
    23932393        r = query(q).getresult()[0]
    23942394        self.assertEqual(r, (3, 0))
    2395         self.assertRaises(pg.ProgrammingError, truncate, 'test_parent')
     2395        self.assertRaises(pg.NotSupportedError, truncate, 'test_parent')
    23962396        truncate('test_parent', cascade=True)
    23972397        r = query(q).getresult()[0]
     
    27782778        query("insert into test_table values (8)")
    27792779        self.db.release('before8')
    2780         self.assertRaises(pg.ProgrammingError, self.db.rollback, 'before8')
     2780        self.assertRaises(pg.InternalError, self.db.rollback, 'before8')
    27812781        self.db.commit()
    27822782        self.db.start()
     
    27872787        self.assertEqual(r, [1, 2, 5, 7, 9])
    27882788        self.db.begin(mode='read only')
    2789         self.assertRaises(pg.ProgrammingError,
     2789        self.assertRaises(pg.InternalError,
    27902790                          query, "insert into test_table values (0)")
    27912791        self.db.rollback()
    27922792        self.db.start(mode='Read Only')
    2793         self.assertRaises(pg.ProgrammingError,
     2793        self.assertRaises(pg.InternalError,
    27942794                          query, "insert into test_table values (0)")
    27952795        self.db.abort()
     
    28192819                query("insert into test_table values (6)")
    28202820                query("insert into test_table values (-1)")
    2821         except pg.ProgrammingError as error:
     2821        except pg.IntegrityError as error:
    28222822            self.assertTrue('check' in str(error))
    28232823        with self.db:
     
    31673167            self.assertEqual(r['t'], '{a,b,c}')
    31683168        r = dict(i="1, 2, 3", t="'a', 'b', 'c'")
    3169         self.assertRaises(pg.ProgrammingError, self.db.insert, 'arraytest', r)
     3169        self.assertRaises(pg.DataError, self.db.insert, 'arraytest', r)
    31703170
    31713171    def testArrayOfIds(self):
     
    36903690        try:
    36913691            self.db.query("select 'k=>v'::hstore")
    3692         except pg.ProgrammingEror:
     3692        except pg.DatabaseError:
    36933693            try:
    36943694                self.db.query("create extension hstore")
    3695             except pg.ProgrammingError:
     3695            except pg.DatabaseError:
    36963696                self.skipTest("hstore extension not enabled")
    36973697        d = {'k': 'v', 'foo': 'bar', 'baz': 'whatever',
  • trunk/tests/test_dbapi20.py

    r822 r823  
    464464            con.close()
    465465
     466    def test_integrity_error(self):
     467        table = self.table_prefix + 'booze'
     468        con = self._connect()
     469        try:
     470            cur = con.cursor()
     471            cur.execute("set client_min_messages = warning")
     472            cur.execute("create table %s (i int primary key)" % table)
     473            cur.execute("insert into %s values (1)" % table)
     474            cur.execute("insert into %s values (2)" % table)
     475            self.assertRaises(pgdb.IntegrityError, cur.execute,
     476                "insert into %s values (1)" % table)
     477        finally:
     478            con.close()
     479
    466480    def test_sqlstate(self):
    467481        con = self._connect()
     
    470484            cur.execute("select 1/0")
    471485        except pgdb.DatabaseError as error:
    472             self.assertTrue(isinstance(error, pgdb.ProgrammingError))
     486            self.assertTrue(isinstance(error, pgdb.DataError))
    473487            # the SQLSTATE error code for division by zero is 22012
    474488            self.assertEqual(error.sqlstate, '22012')
     
    603617            cur = con.cursor()
    604618            cur.execute("select 'k=>v'::hstore")
    605         except pgdb.ProgrammingError:
     619        except pgdb.DatabaseError:
    606620            try:
    607621                cur.execute("create extension hstore")
    608             except pgdb.ProgrammingError:
     622            except pgdb.DatabaseError:
    609623                self.skipTest("hstore extension not enabled")
    610624        finally:
     
    9981012            self.assertEqual(cur.fetchone()[0], 3)
    9991013            sql = 'select 1/0'  # cannot be executed
    1000             self.assertRaises(pgdb.ProgrammingError, cur.execute, sql)
     1014            self.assertRaises(pgdb.DataError, cur.execute, sql)
    10011015            cur.close()
    10021016            con.rollback()
     
    10701084                    cur.execute("insert into %s values (3)" % table)
    10711085                    cur.execute("insert into %s values (4)" % table)
    1072             except con.ProgrammingError as error:
     1086            except con.IntegrityError as error:
    10731087                self.assertTrue('check' in str(error).lower())
    10741088            with con:
  • trunk/tests/test_dbapi20_copy.py

    r772 r823  
    140140        cur.close()
    141141        con.commit()
     142        cur = con.cursor()
     143        try:
     144            cur.execute("set client_encoding=utf8")
     145            cur.execute("select 'Plácido and José'").fetchone()
     146        except (pgdb.DataError, pgdb.NotSupportedError):
     147            cls.data[1] = (1941, 'Plaacido Domingo')
     148            cls.data[2] = (1946, 'Josee Carreras')
     149            cls.can_encode = False
     150        cur.close()
    142151        con.close()
    143152        cls.cls_set_up = True
     
    175184            (1941, 'Plácido Domingo'),
    176185            (1946, 'José Carreras')]
     186
     187    can_encode = True
    177188
    178189    @property
     
    266277
    267278        def test_input_unicode(self):
     279            if not self.can_encode:
     280                self.skipTest('database does not support utf8')
    268281            self.copy_from(u'43\tWÃŒrstel, KÀse!')
    269282            self.assertEqual(self.table_data, [(43, 'WÃŒrstel, KÀse!')])
     
    395408        con = cls.connect()
    396409        cur = con.cursor()
     410        cur.execute("set client_encoding=utf8")
    397411        cur.execute("insert into copytest values (%d, %s)", cls.data)
    398412        cur.close()
Note: See TracChangeset for help on using the changeset viewer.