| | 1 | # -*- coding: iso8859-1 -*- |
| | 2 | # |
| | 3 | # Copyright (C) 2005 Edgewall Software |
| | 4 | # Copyright (C) 2005 Jeff Weiss <trac@jeffweiss.org> |
| | 5 | # All rights reserved. |
| | 6 | # |
| | 7 | # This software is licensed as described in the file COPYING, which |
| | 8 | # you should have received as part of this distribution. The terms |
| | 9 | # are also available at http://trac.edgewall.com/license.html. |
| | 10 | # |
| | 11 | # This software consists of voluntary contributions made by many |
| | 12 | # individuals. For the exact contribution history, see the revision |
| | 13 | # history and logs, available at http://projects.edgewall.com/trac/. |
| | 14 | # |
| | 15 | # Author: Jeff Weiss <trac@jeffweiss.org> |
| | 16 | |
| | 17 | from trac.core import * |
| | 18 | from trac.db.api import IDatabaseConnector |
| | 19 | from trac.db.util import ConnectionWrapper |
| | 20 | |
| | 21 | import MySQLdb |
| | 22 | |
| | 23 | class MySQLConnector(Component): |
| | 24 | """MySQL database support. Still extremely experimental! |
| | 25 | Currently only supports database urls of the following formats: |
| | 26 | mysql://user:password@host/database |
| | 27 | mysql://user:password@host:port/database |
| | 28 | """ |
| | 29 | |
| | 30 | implements(IDatabaseConnector) |
| | 31 | |
| | 32 | def get_supported_schemes(self): |
| | 33 | return [('mysql', 1)] |
| | 34 | |
| | 35 | def get_connection(self, path, user=None, password=None, host=None, |
| | 36 | port=None, params={}): |
| | 37 | return MySQLConnection(path, user, password, host, port, params) |
| | 38 | |
| | 39 | def init_db(self, path, user=None, password=None, host=None, port=None, |
| | 40 | params={}): |
| | 41 | cnx = self.get_connection(path, user, password, host, port, params) |
| | 42 | cursor = cnx.cursor() |
| | 43 | from trac.db_default import schema |
| | 44 | for table in schema: |
| | 45 | for stmt in self.to_sql(table): |
| | 46 | cursor.execute(stmt) |
| | 47 | cnx.commit() |
| | 48 | |
| | 49 | def to_sql(self, table): |
| | 50 | sql = ["CREATE TABLE %s (" % table.name] |
| | 51 | coldefs = [] |
| | 52 | textkeys = [] |
| | 53 | textcols = [] |
| | 54 | mytablekey = [] |
| | 55 | num_key_text = 0 |
| | 56 | for column in table.columns: |
| | 57 | ctype = column.type |
| | 58 | if column.auto_increment: |
| | 59 | ctype += " AUTO_INCREMENT" |
| | 60 | if column.name in table.key: |
| | 61 | mytablekey.append(column.name) |
| | 62 | if column.type == 'text': |
| | 63 | num_key_text += 1 |
| | 64 | textkeys.append(column.name) |
| | 65 | if column.type == 'text': |
| | 66 | textcols.append(column.name) |
| | 67 | coldefs.append(" `%s` %s" % (column.name, ctype)) |
| | 68 | if len(table.key) >= 1: |
| | 69 | if num_key_text == 0: |
| | 70 | num_key_text = 1 |
| | 71 | size = 767 / num_key_text |
| | 72 | keysize = "(%s)" % size |
| | 73 | for key in textkeys: |
| | 74 | if key in mytablekey: |
| | 75 | mytablekey.remove(key) |
| | 76 | sizedkey = '`' + key + '`' + keysize |
| | 77 | mytablekey.append(sizedkey) |
| | 78 | coldefs.append(" CONSTRAINT %s_pk PRIMARY KEY (%s)" |
| | 79 | % (table.name, ','.join(mytablekey))) |
| | 80 | sql.append(',\n'.join(coldefs) + '\n)') |
| | 81 | yield '\n'.join(sql) |
| | 82 | # This does not work because what I need to do is not add something about |
| | 83 | # the index size for the text fields because MySQL apparrently can't index |
| | 84 | # on the entire length of the text field |
| | 85 | for index in table.indices: |
| | 86 | myidxcols = set([]) |
| | 87 | processedidx = set([]) |
| | 88 | for col in index.columns: |
| | 89 | if col in textcols: |
| | 90 | myidxcols.add(col) |
| | 91 | myidx = set(index.columns) - myidxcols |
| | 92 | for col in myidx: |
| | 93 | escaped_col = '`'+col+'`' |
| | 94 | processedidx.add(escaped_col) |
| | 95 | if len(myidxcols) > 0: |
| | 96 | size = 767 / len(myidxcols) |
| | 97 | idxsize = "(%s)" % size |
| | 98 | for col in myidxcols: |
| | 99 | sizedidx = '`'+col+'`'+idxsize |
| | 100 | processedidx.add(sizedidx) |
| | 101 | yield "CREATE INDEX %s_%s_idx ON %s (%s)" % (table.name, |
| | 102 | '_'.join(index.columns), table.name, ','.join(processedidx)) |
| | 103 | |
| | 104 | |
| | 105 | class MySQLConnection(ConnectionWrapper): |
| | 106 | """Connection wrapper for MySQL.""" |
| | 107 | |
| | 108 | poolable = True |
| | 109 | |
| | 110 | def __init__(self, path, usr=None, password=None, hst=None, prt=None, |
| | 111 | params={}): |
| | 112 | cnx = MySQLdb.connect(db=path[1:], user=usr, passwd=password, host=hst, port=prt) |
| | 113 | ConnectionWrapper.__init__(self, cnx) |
| | 114 | |
| | 115 | def cast(self, column, type): |
| | 116 | # Temporary hack needed for the union of selects in the search module |
| | 117 | return 'CAST(%s AS %s)' % (column, type) |
| | 118 | |
| | 119 | def like(self): |
| | 120 | # Temporary hack needed for the case-insensitive string matching in the |
| | 121 | # search module |
| | 122 | return 'LIKE' |
| | 123 | |
| | 124 | def get_last_id(self, cursor, table, column='id'): |
| | 125 | sql = "SELECT MAX(`%s`) FROM %s" % (column, table) |
| | 126 | cursor.execute(sql) |
| | 127 | return cursor.fetchone()[0] |