Example #1
0
 def _get_db(self):
     con = self.connection
     if con is not None:
         con = con()
     if con is None:
         raise ProgrammingError("cursor closed")
     return con
Example #2
0
    def query(self, sql_string, max_rows=1000):
        """ Execute ``sql_string`` and return at most ``max_rows``.
        """
        self._use_TM and self._register()
        desc = None
        rows = ()

        for qs in filter(None, [q.strip() for q in sql_string.split('\0')]):
            qtype = qs.split(None, 1)[0].upper()
            if qtype == 'SELECT' and max_rows:
                qs = '%s LIMIT %d' % (qs, max_rows)
            db_results = self._query(qs)

            if desc is not None and \
               db_results and \
               db_results.describe() != desc:
                msg = 'Multiple select schema are not allowed.'
                raise ProgrammingError(msg)

            if db_results:
                desc = db_results.describe()
                rows = db_results.fetch_row(max_rows)
            else:
                desc = None

            if qtype == 'CALL':
                # For stored procedures, skip the status result
                self.db.next_result()

        if desc is None:
            return (), ()

        items = []
        for info in desc:
            items.append({'name': info[0],
                          'type': self.defs.get(info[1], 't'),
                          'width': info[2],
                          'null': info[6]})

        return items, rows
Example #3
0
class DB(TM):
    """This is the ZMySQLDA Database Connection Object."""

    conv = conversions.copy()
    conv[FIELD_TYPE.LONG] = int
    conv[FIELD_TYPE.DATETIME] = DateTime_or_None
    conv[FIELD_TYPE.DATE] = DateTime_or_None
    conv[FIELD_TYPE.DECIMAL] = float
    conv[FIELD_TYPE.BIT] = ord_or_None
    del conv[FIELD_TYPE.TIME]

    _sort_key = TM._sort_key

    def __init__(self, connection):
        """
          Parse the connection string.
          Initiate a trial connection with the database to check
          transactionality once instead of once per DB instance.
        """
        self._connection = connection
        self._parse_connection_string()
        self._forceReconnection()
        transactional = self.db.server_capabilities & CLIENT.TRANSACTIONS
        if self._try_transactions == '-':
            transactional = 0
        elif not transactional and self._try_transactions == '+':
            raise NotSupportedError, "transactions not supported by this server"
        self._transactions = transactional
        self._use_TM = transactional or self._mysql_lock

    def _parse_connection_string(self):
        self._mysql_lock = self._try_transactions = None
        self._kw_args = kwargs = {'conv': self.conv}
        items = self._connection.split()
        if not items:
            return
        if items[0] == "~":
            kwargs['compress'] = True
            del items[0]
        if items[0][0] == "*":
            self._mysql_lock = items.pop(0)[1:]
        db = items.pop(0)
        if '@' in db:
            db, host = db.split('@', 1)
            if os.path.isabs(host):
                kwargs['unix_socket'] = host
            else:
                if host.startswith('['):
                    host, port = host[1:].split(']', 1)
                    if port.startswith(':'):
                        kwargs['port'] = int(port[1:])
                elif ':' in host:
                    host, port = host.split(':', 1)
                    kwargs['port'] = int(port)
                kwargs['host'] = host
        if db:
            if db[0] in '+-':
                self._try_transactions = db[0]
                db = db[1:]
            if db:
                kwargs['db'] = db
        if items:
            kwargs['user'] = items.pop(0)
            if items:
                kwargs['passwd'] = items.pop(0)
                if items:  # BBB
                    assert 'unix_socket' not in kwargs
                    warnings.warn(
                        "use '<db>@<unix_socket> ...' syntax instead",
                        DeprecationWarning)
                    kwargs['unix_socket'] = items.pop(0)

    defs = {
        FIELD_TYPE.CHAR: "i",
        FIELD_TYPE.DATE: "d",
        FIELD_TYPE.DATETIME: "d",
        FIELD_TYPE.DECIMAL: "n",
        FIELD_TYPE.DOUBLE: "n",
        FIELD_TYPE.FLOAT: "n",
        FIELD_TYPE.INT24: "i",
        FIELD_TYPE.LONG: "i",
        FIELD_TYPE.LONGLONG: "l",
        FIELD_TYPE.SHORT: "i",
        FIELD_TYPE.TIMESTAMP: "d",
        FIELD_TYPE.TINY: "i",
        FIELD_TYPE.YEAR: "i",
    }

    _p_oid = _p_changed = _registered = None

    def __del__(self):
        self.db.close()

    def _forceReconnection(self):
        self.db = MySQLdb.connect(**self._kw_args)

    def tables(self, rdb=0, _care=('TABLE', 'VIEW')):
        """Returns a list of tables in the current database."""
        r = []
        a = r.append
        result = self._query("SHOW TABLES")
        row = result.fetch_row(1)
        while row:
            a({'TABLE_NAME': row[0][0], 'TABLE_TYPE': 'TABLE'})
            row = result.fetch_row(1)
        return r

    def columns(self, table_name):
        """Returns a list of column descriptions for 'table_name'."""
        try:
            c = self._query('SHOW COLUMNS FROM %s' % table_name)
        except Exception:
            return ()
        from string import join
        r = []
        for Field, Type, Null, Key, Default, Extra in c.fetch_row(0):
            info = {}
            field_default = Default and "DEFAULT %s" % Default or ''
            if Default: info['Default'] = Default
            if '(' in Type:
                end = Type.rfind(')')
                short_type, size = Type[:end].split('(', 1)
                if short_type not in ('set', 'enum'):
                    if ',' in size:
                        info['Scale'], info['Precision'] = \
                                       map(int, size.split(',', 1))
                    else:
                        info['Scale'] = int(size)
            else:
                short_type = Type
            if short_type in field_icons:
                info['Icon'] = short_type
            else:
                info['Icon'] = icon_xlate.get(short_type, "what")
            info['Name'] = Field
            info['Type'] = type_xlate.get(short_type, 'string')
            info['Extra'] = Extra,
            info['Description'] = join([
                Type, field_default, Extra or '',
                key_types.get(Key, Key or ''), Null != 'YES' and 'NOT NULL'
                or ''
            ]),
            info['Nullable'] = Null == 'YES'
            if Key:
                info['Index'] = 1
            if Key == 'PRI':
                info['PrimaryKey'] = 1
                info['Unique'] = 1
            elif Key == 'UNI':
                info['Unique'] = 1
            r.append(info)
        return r

    def _query(self, query, allow_reconnect=False):
        """
          Send a query to MySQL server.
          It reconnects automatically if needed and the following conditions are
          met:
           - It has not just tried to reconnect (ie, this function will not
             attempt to connect twice per call).
           - This connection is not transactional and has set not MySQL locks,
             because they are bound to the connection. This check can be
             overridden by passing allow_reconnect with True value.
        """
        try:
            self.db.query(query)
        except OperationalError, m:
            if m[0] in query_syntax_error:
                raise OperationalError(m[0], '%s: %s' % (m[1], query))
            if m[0] in lock_error:
                raise ConflictError('%s: %s: %s' % (m[0], m[1], query))
            if not allow_reconnect and self._use_TM or \
              m[0] not in hosed_connection:
                LOG('ZMySQLDA', ERROR, 'query failed: %s' % (query, ))
                raise
            # Hm. maybe the db is hosed.  Let's restart it.
            self._forceReconnection()
            self.db.query(query)
        except ProgrammingError, exception:
            LOG('ZMySQLDA', ERROR, 'query failed: %s' % (query, ))
            # XXX sometimes, after a programming error, the database object
            # gets fully broken and non-functional. So recover it by
            # recreation.
            self._forceReconnection()
            if exception[0] == ER.PARSE_ERROR:
                # You have an error in your SQL syntax
                # Replace MySQL brain dead error message with a more meaningful
                # one. (MySQL only reports the SQL query *from* the error place,
                # which strips important contextual information).
                error_text = exception[1]
                prefix, suffix = error_text.split("'", 1)
                if prefix == "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ":
                    sql, suffix = suffix.rsplit("'", 1)
                    try:
                        line_number = int(suffix.rsplit(' ', 1)[-1])
                    except TypeError:
                        pass
                    else:
                        reference_sql = query
                        split_reference_sql = reference_sql.split('\n')
                        candidate_sql = '\n'.join(
                            split_reference_sql[line_number - 1:])
                        error_position = len(reference_sql) - len(
                            candidate_sql) + candidate_sql.find(sql)
                        if error_position > -1:
                            raise ProgrammingError(
                                exception[0], "%s '%s' HERE '%s' %s" %
                                (prefix, reference_sql[:error_position],
                                 reference_sql[error_position:], suffix))
            raise exception