Edgewall Software

Ticket #986: trac-mysql-r2946.2.patch

File trac-mysql-r2946.2.patch, 11.9 KB (added by Andres Salomon <dilinger@…>, 3 years ago)

mysql patch w/ bugfix for deleting sessions; only run delete query when there's actually something to delete.

  • (a) /dev/null vs. (b) trac/db/mysql_backend.py

    === added file 'trac/db/mysql_backend.py'
    a b  
     1# -*- coding: iso8859-1 -*- 
     2# 
     3# Copyright (C) 2005 Edgewall Software 
     4# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> 
     5# Copyright (C) 2005 Jeff Weiss <trac@jeffweiss.org> 
     6# Copyright (C) 2006 Andres Salomon <dilinger@athenacr.com> 
     7# All rights reserved. 
     8# 
     9# This software is licensed as described in the file COPYING, which 
     10# you should have received as part of this distribution. The terms 
     11# are also available at http://trac.edgewall.com/license.html. 
     12# 
     13# This software consists of voluntary contributions made by many 
     14# individuals. For the exact contribution history, see the revision 
     15# history and logs, available at http://projects.edgewall.com/trac/. 
     16# 
     17# Derived from postgres_backend.py 
     18# Author: Christopher Lenz <cmlenz@gmx.de>  
     19# Author: Jeff Weiss <trac@jeffweiss.org> 
     20 
     21from trac.core import * 
     22from trac.db.api import IDatabaseConnector 
     23from trac.db.util import ConnectionWrapper 
     24 
     25class MySQLConnector(Component): 
     26    """MySQL database support.  Still extremely experimental! 
     27Currently only supports database urls of the following formats: 
     28mysql://user:password@host/database 
     29mysql://user:password@host:port/database 
     30""" 
     31 
     32    implements(IDatabaseConnector) 
     33 
     34    def get_supported_schemes(self): 
     35        return [('mysql', 1)] 
     36 
     37    def get_connection(self, path, user=None, password=None, host=None, 
     38                       port=None, params={}): 
     39        return MySQLConnection(path, user, password, host, port, params) 
     40 
     41    def init_db(self, path, user=None, password=None, host=None, port=None, 
     42                params={}): 
     43        cnx = self.get_connection(path, user, password, host, port, params) 
     44        cursor = cnx.cursor() 
     45        from trac.db_default import schema 
     46        for table in schema: 
     47            for stmt in self.to_sql(table): 
     48                self.env.log.debug(stmt) 
     49                cursor.execute(stmt) 
     50        cnx.commit() 
     51 
     52    def _collist(self, table, columns): 
     53        """ 
     54        Take a list of columns and impose limits on each so that indexing 
     55        works properly.  Some Versions of MySQL limit each index prefix to 
     56        500 bytes total, with a max of 255 bytes per column. 
     57        """ 
     58        cols = [] 
     59        limit = 500 / len(columns) 
     60        if limit > 255: 
     61            limit = 255 
     62        for c in columns: 
     63            name = '`%s`' % c 
     64            table_col = filter((lambda x: x.name == c), table.columns) 
     65            if len(table_col) == 1 and table_col[0].type.lower() == 'text': 
     66                name += '(%s)' % limit 
     67            # For non-text columns, we simply throw away the extra bytes. 
     68            # That could certainly be optimized better, but for now let's KISS. 
     69            cols.append(name) 
     70        return ','.join(cols) 
     71 
     72    def to_sql(self, table): 
     73        sql = ['CREATE TABLE %s (' % table.name] 
     74        coldefs = [] 
     75        for column in table.columns: 
     76            ctype = column.type 
     77            if column.auto_increment: 
     78                ctype = 'INT UNSIGNED NOT NULL AUTO_INCREMENT' 
     79                # Override the column type, as a text field cannot 
     80                # use auto_increment. 
     81                column.type = 'int' 
     82            coldefs.append('    `%s` %s' % (column.name, ctype)) 
     83        if len(table.key) > 0: 
     84            coldefs.append('    PRIMARY KEY (%s)' % 
     85                           self._collist(table, table.key)) 
     86        sql.append(',\n'.join(coldefs) + '\n)') 
     87        yield '\n'.join(sql) 
     88        for index in table.indices: 
     89            yield 'CREATE INDEX %s_%s_idx ON %s (%s);' % (table.name, 
     90                  '_'.join(index.columns), table.name, 
     91                  self._collist(table, index.columns)) 
     92 
     93class MySQLConnection(ConnectionWrapper): 
     94    """Connection wrapper for MySQL.""" 
     95 
     96    poolable = True 
     97 
     98    def __init__(self, path, user=None, password=None, host=None, 
     99                 port=None, params={}): 
     100        import MySQLdb 
     101 
     102        if path.startswith('/'): 
     103            path = path[1:] 
     104        if port == None: 
     105            port = 3306 
     106        cnx = MySQLdb.connect(db=path, user=user, passwd=password, host=host, port=port) 
     107        ConnectionWrapper.__init__(self, cnx) 
     108 
     109    def cast(self, column, type): 
     110        # Temporary hack needed for the union of selects in the search module 
     111        return 'CAST(%s AS %s)' % (column, type) 
     112 
     113    def like(self): 
     114        # Temporary hack needed for the case-insensitive string matching in the 
     115        # search module 
     116        return 'LIKE' 
     117 
     118    def get_last_id(self, cursor, table, column='id'): 
     119        return self.cnx.insert_id() 
  • trac/db/tests/api.py

    === modified file 'trac/db/tests/api.py'
     
    4040                                       'path': '/trac'}), 
    4141                         _parse_db_str('postgres://john:letmein@localhost:9431/trac')) 
    4242 
     43    def test_mysql_simple(self): 
     44        self.assertEqual(('mysql', {'host': 'localhost', 'path': '/trac'}), 
     45                         _parse_db_str('mysql://localhost/trac')) 
     46 
     47    def test_mysql_with_creds(self): 
     48        self.assertEqual(('mysql', {'user': 'john', 'password': 'letmein', 
     49                                    'host': 'localhost', 'port': 3306, 
     50                                    'path': '/trac'}), 
     51                         _parse_db_str('mysql://john:letmein@localhost:3306/trac')) 
    4352 
    4453def suite(): 
    4554    return unittest.makeSuite(ParseConnectionStringTestCase,'test') 
  • trac/db_default.py

    === modified file 'trac/db_default.py'
     
    148148        Column('id', auto_increment=True), 
    149149        Column('author'), 
    150150        Column('title'), 
    151         Column('sql'), 
     151        Column('query'), 
    152152        Column('description')], 
    153153] 
    154154 
     
    379379             ('name', 'value'), 
    380380               (('database_version', str(db_version)),)), 
    381381           ('report', 
    382              ('author', 'title', 'sql', 'description'), 
     382             ('author', 'title', 'query', 'description'), 
    383383               __mkreports(reports))) 
    384384 
    385385default_config = \ 
     
    442442 
    443443default_components = ('trac.About', 'trac.attachment', 
    444444                      'trac.db.postgres_backend', 'trac.db.sqlite_backend', 
     445                      'trac.db.mysql_backend', 
    445446                      'trac.mimeview.enscript', 'trac.mimeview.patch', 
    446447                      'trac.mimeview.php', 'trac.mimeview.rst', 
    447448                      'trac.mimeview.silvercity', 'trac.mimeview.txtl', 
  • trac/ticket/report.py

    === modified file 'trac/ticket/report.py'
     
    148148            req.redirect(self.env.href.report()) 
    149149 
    150150        title = req.args.get('title', '') 
    151         sql = req.args.get('sql', '') 
     151        query = req.args.get('query', '') 
    152152        description = req.args.get('description', '') 
    153153        cursor = db.cursor() 
    154         cursor.execute("INSERT INTO report (title,sql,description) " 
    155                        "VALUES (%s,%s,%s)", (title, sql, description)) 
     154        cursor.execute("INSERT INTO report (title,query,description) " 
     155                       "VALUES (%s,%s,%s)", (title, query, description)) 
    156156        id = db.get_last_id(cursor, 'report') 
    157157        db.commit() 
    158158        req.redirect(self.env.href.report(id)) 
     
    176176 
    177177        if not req.args.has_key('cancel'): 
    178178            title = req.args.get('title', '') 
    179             sql = req.args.get('sql', '') 
     179            query = req.args.get('query', '') 
    180180            description = req.args.get('description', '') 
    181181            cursor = db.cursor() 
    182             cursor.execute("UPDATE report SET title=%s,sql=%s,description=%s " 
    183                            "WHERE id=%s", (title, sql, description, id)) 
     182            cursor.execute("UPDATE report SET title=%s,query=%s,description=%s " 
     183                           "WHERE id=%s", (title, query, description, id)) 
    184184            db.commit() 
    185185        req.redirect(self.env.href.report(id)) 
    186186 
     
    204204    def _render_editor(self, req, db, id, copy=False): 
    205205        if id == -1: 
    206206            req.perm.assert_permission('REPORT_CREATE') 
    207             title = sql = description = '' 
     207            title = query = description = '' 
    208208        else: 
    209209            req.perm.assert_permission('REPORT_MODIFY') 
    210210            cursor = db.cursor() 
    211             cursor.execute("SELECT title,description,sql FROM report " 
     211            cursor.execute("SELECT title,description,query FROM report " 
    212212                           "WHERE id=%s", (id,)) 
    213213            row = cursor.fetchone() 
    214214            if not row: 
     
    216216                                     'Invalid Report Number') 
    217217            title = row[0] or '' 
    218218            description = row[1] or '' 
    219             sql = row[2] or '' 
     219            query = row[2] or '' 
    220220 
    221221        if copy: 
    222222            title += ' (copy)' 
     
    233233        req.hdf['report.id'] = id 
    234234        req.hdf['report.mode'] = 'edit' 
    235235        req.hdf['report.title'] = title 
    236         req.hdf['report.sql'] = sql 
     236        req.hdf['report.sql'] = query 
    237237        req.hdf['report.description'] = description 
    238238 
    239239    def _render_view(self, req, db, id): 
     
    426426            description = 'This is a list of reports available.' 
    427427        else: 
    428428            cursor = db.cursor() 
    429             cursor.execute("SELECT title,sql,description from report " 
     429            cursor.execute("SELECT title,query,description from report " 
    430430                           "WHERE id=%s", (id,)) 
    431431            row = cursor.fetchone() 
    432432            if not row: 
  • trac/upgrades/db17.py

    === modified file 'trac/upgrades/db17.py'
     
    22 
    33def do_upgrade(env, ver, cursor): 
    44    """Rename the columns `kind` and `change` in the `node_change` table for 
    5     compatibity with MySQL. 
     5    compatibity with MySQL, as well as the `sql` column in the `reports` table. 
    66    """ 
     7    db_connector, _ = DatabaseManager(env)._get_connector() 
     8 
     9    # alter node_change table 
    710    cursor.execute("CREATE TEMP TABLE nc_old AS SELECT * FROM node_change") 
    811    cursor.execute("DROP TABLE node_change") 
    912 
     
    1619        Column('base_rev'), 
    1720        Index(['rev']) 
    1821    ] 
    19     db_connector, _ = DatabaseManager(env)._get_connector() 
    2022    for stmt in db_connector.to_sql(table): 
    2123        cursor.execute(stmt) 
    2224 
    2325    cursor.execute("INSERT INTO node_change (rev,path,node_type,change_type," 
    2426                   "base_path,base_rev) SELECT rev,path,kind,change," 
    2527                   "base_path,base_rev FROM nc_old") 
     28 
     29    # alter report table 
     30    cursor.execute("CREATE TEMP TABLE report_old AS SELECT * FROM report") 
     31    cursor.execute("DROP TABLE report") 
     32 
     33    table = Table('report', key='id')[ 
     34        Column('id', auto_increment=True), 
     35        Column('author'), 
     36        Column('title'), 
     37        Column('query'), 
     38        Column('description') 
     39    ] 
     40    for stmt in db_connector.to_sql(table): 
     41        cursor.execute(stmt) 
     42 
     43    cursor.execute("INSERT INTO report (id,author,title,query,description) " 
     44                   "SELECT id,author,title,sql,description FROM report_old") 
  • trac/web/session.py

    === modified file 'trac/web/session.py'
     
    181181            # changed as to minimize the purging. 
    182182            mintime = now - PURGE_AGE 
    183183            self.env.log.debug('Purging old, expired, sessions.') 
    184             cursor.execute("DELETE FROM session WHERE authenticated=0 AND " 
    185                            "sid IN (SELECT sid FROM session WHERE " 
    186                            "var_name='last_visit' AND var_value < %s)", 
     184            cursor.execute("SELECT DISTINCT sid FROM session WHERE " 
     185                           "var_name='last_visit' AND var_value < %s", 
    187186                           (mintime,)) 
    188  
     187            args = cursor.fetchall() 
     188            if args: 
     189                sql = 'DELETE FROM session WHERE authenticated = 0 AND (' 
     190                sql += ' OR '.join(['sid = %s'] * len(args)) + ')' 
     191                cursor.execute(sql, args) 
    189192            db.commit()