Execute query, catch and log errors.

def _execute(self, sql, opt=None, callno = 0):
        """ Execute query, catch and log errors.
        """

        self._logger.debug("SQL: " + sql + "  params: " + unicode(opt))
        try:
            self._curs_pg.execute(sql, opt)
        except psycopg2.InternalError as exc:
            self._con_pg.rollback()

            # NOTE: psycopg2 is unable to differentiate between exceptions
            # thrown by stored procedures and certain other database internal
            # exceptions, thus we do not know if the exception in question is
            # raised from our stored procedure or from some other error. In
            # addition, postgresql is unable to pass any error code for the
            # exception and so the only thing which we can look at is the actual
            # text string.
            #
            # Exceptions raised by our stored procedures will all start with an
            # error code followed by a colon, which separates the error code
            # from the text string that follows next. If the error text does not
            # follow this format it is likely not one of "our" exceptions and so
            # we throw (and log) a more general exception.

            # determine if it's "one of our" exceptions or something else
            if len(unicode(exc).split(":")) < 2:
                raise NipapError(exc)
            code = unicode(exc).split(":", 1)[0]
            try:
                int(code)
            except:
                raise NipapError(exc)

            text = unicode(exc).splitlines()[0].split(":", 1)[1]

            if code == '1200':
                raise NipapValueError(text)

            estr = "Internal database error: %s" % exc
            self._logger.error(estr)
            raise NipapError(unicode(exc))

        except psycopg2.IntegrityError as exc:
            self._con_pg.rollback()

            # this is a duplicate key error
            if exc.pgcode == "23505":
                # figure out which column it is and retrieve the database
                # description for that column
                m = re.match(r'.*"([^"]+)"', exc.pgerror)
                if m is None:
                    raise NipapDuplicateError("Objects primary keys already exist")
                cursor = self._con_pg.cursor()
                cursor.execute("""  SELECT
                                        obj_description(oid)
                                    FROM pg_class
                                    WHERE relname = %(relname)s""",
                                { 'relname': m.group(1) })
                column_desc = '<unknown>'
                for desc in cursor:
                    column_desc = unicode(desc[0])

                # figure out the value for the duplicate value
                column_value = None
                try:
                    m = re.match(r'.*=\(([^)]+)\) already exists.', exc.pgerror.splitlines()[1])
                    if m is not None:
                        column_value = m.group(1)
                except:
                    pass
                else:
                    raise NipapDuplicateError("Duplicate value for '" +
                        unicode(column_desc) + "', the value '" +
                        unicode(column_value) + "' is already in use.")

                raise NipapDuplicateError("Duplicate value for '" +
                    unicode(column_desc) +
                    "', the value you have inputted is already in use.")

            self._logger.exception("Unhandled database IntegrityError:")
            raise NipapError("Unhandled integrity error.")

        except psycopg2.DataError as exc:
            self._con_pg.rollback()

            m = re.search('invalid cidr value: "([^"]+)"', exc.pgerror)
            if m is not None:
                strict_prefix = unicode(IPy.IP(m.group(1), make_net = True))
                estr = "Invalid prefix (%s); bits set to right of mask. Network address for current mask: %s" % (m.group(1), strict_prefix)
                raise NipapValueError(estr)

            m = re.search('invalid input syntax for(?: type)? (\w+): "([^"]+)"', exc.pgerror)
            if m is not None:
                if m.group(1) in ["cidr", "inet"]:
                    estr = "Invalid syntax for prefix (%s)" % m.group(2)
                else:
                    estr = "Invalid syntax for %s (%s)" % (m.group(1), m.group(2))
                raise NipapValueError(estr)

            self._logger.exception("Unhandled database DataError:")
            raise NipapError("Unhandled data error.")

        except psycopg2.Error as exc:
            try:
                self._con_pg.rollback()
            except psycopg2.Error:
                pass

            estr = "Unable to execute query: %s" % exc
            self._logger.error(estr)

            # abort if we've already tried to reconnect
            if callno > 0:
                self._logger.error(estr)
                raise NipapError(estr)

            # reconnect to database and retry query
            self._logger.info("Reconnecting to database...")
            self._connect_db()

            return self._execute(sql, opt, callno + 1)

        except psycopg2.Warning as warn:
            self._logger.warning(unicode(warn))