[PATCH] [database/exception] include the query information in database error for better debuging

Laurent Peuch cortex at worlddomination.be
Tue May 21 11:20:02 CEST 2019


On Tue, May 21, 2019 at 11:10:34AM +0200, Laurent Peuch wrote:
> # HG changeset patch
> # User Laurent Peuch <cortex at worlddomination.be>
> # Date 1558428691 -7200
> #      Tue May 21 10:51:31 2019 +0200
> # Node ID a2465304bbef8a3dfdfa9b6fd87f9d59fd7028c7
> # Parent  14b4252b57e0930f47a9f9fffba0ffc15dd44300
> [database/exception] include the query information in database error for better debuging
> 
> This will change an exception from something like :
> 
>     psycopg2.IntegrityError: null value in column "asource" violates not-null constraint
>     DETAIL:  Failing row contains (341471, CWRType, null, null).
> 
> To:
> 
>     psycopg2.IntegrityError: when doing the query 'INSERT INTO entities ( eid, type ) VALUES ( %(eid)s, %(type)s )' with the args '{'type': u'CWRType', 'eid': 341471}' got the error 'null value in column "asource" violates not-null constraint
>     DETAIL:  Failing row contains (341471, CWRType, null, null).'
> 
> Tested with sqlite3 and postgresql.
> 
> diff --git a/cubicweb/server/sources/native.py b/cubicweb/server/sources/native.py
> --- a/cubicweb/server/sources/native.py
> +++ b/cubicweb/server/sources/native.py
> @@ -727,6 +727,9 @@ class NativeSQLSource(SQLAdapterMixIn, A
>                          # sqlite3 (old)
>                          raise ViolatedConstraint(cnx, cstrname=mo.group(1),
>                                                   query=query)
> +            ex.args = ("when doing the query '%s' with the args '%s' got the error '%s'" % (
> +                query, args, str(ex).strip()
> +            ),)
>              raise
>          return cursor
>  

FYI I've tried a much more complex approach by declaring a new
Exception that wrapped the old one ("raise ex from ex") but ended up
with quite advanced problems and magics solutions to avoid breaking
backward compatibility. But that ended up super magic and complex and
especially not working (because those exceptions are C binding) so I
ended up droping it.

Here is the (edited) diff if you are curious:

changeset:   12739:1d62bfdaf83e
user:        Laurent Peuch <cortex at worlddomination.be>
date:        Tue May 21 10:30:37 2019 +0200
summary:     [WIP] try to wrap database error into a custom exception

diff --git a/cubicweb/_exceptions.py b/cubicweb/_exceptions.py
--- a/cubicweb/_exceptions.py
+++ b/cubicweb/_exceptions.py
@@ -17,7 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Exceptions shared by different cubicweb packages."""
 
-
 from logilab.common.decorators import cachedproperty
 from logilab.common.registry import RegistryException
 
@@ -87,6 +86,28 @@ class UnknownEid(RepositoryError):
     msg = 'No entity with eid %s in the repository'
 
 
+class DatabaseQueryError(Exception):
+    """generic error when a database query failed"""
+
+    def __init__(self, query, args, cnx, exception):
+        self.cnx = cnx
+        message = (
+            "Error when executing the query: '%s' with args '%s' and context '%s':\n%s"
+        ) % (query, args, cnx, exception)
+
+        # magic, we want DatabaseQueryError to still be catchable as a
+        # exception raised by cursor.execute but we can't predict if psycopg2
+        # or other dependencies will be installed so we dynamically add the
+        # exception class to our parents to avoid breaking old "except
+        # Integiry" code for example
+        try:
+            self.__class__.__bases__ = (exception.__class__,) + self.__class__.__bases__
+        except TypeError as e:
+            pass
+
+        super().__init__(message)
+
+
 class UniqueTogetherError(RepositoryError):
     """raised when a unique_together constraint caused an IntegrityError"""
 
diff --git a/cubicweb/server/sources/native.py b/cubicweb/server/sources/native.py
--- a/cubicweb/server/sources/native.py
+++ b/cubicweb/server/sources/native.py
@@ -36,7 +36,8 @@ from logilab.database import get_db_help
 from yams.schema import role_name
 
 from cubicweb import (UnknownEid, AuthenticationError, ValidationError, Binary,
-                      UniqueTogetherError, UndoTransactionException, ViolatedConstraint)
+                      UniqueTogetherError, UndoTransactionException, ViolatedConstraint,
+                      DatabaseQueryError)
 from cubicweb import transaction as tx, server, neg_role, _
 from cubicweb.utils import QueryCache
 from cubicweb.schema import VIRTUAL_RTYPES
@@ -727,7 +728,12 @@ class NativeSQLSource(SQLAdapterMixIn, A
                         # sqlite3 (old)
                         raise ViolatedConstraint(cnx, cstrname=mo.group(1),
                                                  query=query)
-            raise
+            raise exception from ex
         return cursor
 
     @statsd_timeit

-- 

Laurent Peuch -- Bram



More information about the cubicweb-devel mailing list