Edgewall Software

Ticket #6235: ingres-trac-0.11.3.diff

File ingres-trac-0.11.3.diff, 9.6 KB (added by alex.trofast@…, 3 years ago)

Diff against tags/trac-0.11.3 to enable Ingres support

  • setup.py

    diff -Naur -x model.py trac-0.11.3/setup.py trac/setup.py
    old new  
    7676        trac.db.mysql = trac.db.mysql_backend 
    7777        trac.db.postgres = trac.db.postgres_backend 
    7878        trac.db.sqlite = trac.db.sqlite_backend 
     79        trac.db.ingres = trac.db.ingres_backend 
    7980        trac.mimeview.enscript = trac.mimeview.enscript 
    8081        trac.mimeview.patch = trac.mimeview.patch 
    8182        trac.mimeview.php = trac.mimeview.php 
  • trac/db/ingres_backend.py

    diff -Naur -x model.py trac-0.11.3/trac/db/ingres_backend.py trac/trac/db/ingres_backend.py
    old new  
     1# -*- coding: utf-8 -*- 
     2# 
     3# Copyright (C) 2007 Edgewall Software 
     4# Copyright (C) 2007 Alexander Trofast <alex.trofast@ingres.com> 
     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.org/wiki/TracLicense. 
     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://trac.edgewall.org/log/. 
     14# 
     15# Author: Alexander Trofast <alex.trofast@ingres.com> 
     16 
     17 
     18 
     19import re 
     20 
     21from trac.core import * 
     22from trac.db.api import IDatabaseConnector 
     23from trac.db.util import ConnectionWrapper, IterableCursor 
     24from types import * 
     25 
     26import ingresdbi 
     27 
     28_like_escape_re = re.compile(r'([/_%])') 
     29 
     30 
     31class IngresCursor(IterableCursor): 
     32 
     33         
     34        #Changes LIMIT to FIRST and moves it 
     35        def _convert_limit(self, query): 
     36                reglim = re.compile(r"SELECT(.+)LIMIT\s+(\d+)", re.I | re.M | re.S) 
     37                query = reglim.sub(r"SELECT FIRST \2 \1", query) 
     38                return query 
     39 
     40        # Hide away reserved word session within quotes for transportability 
     41        def _replace_session(self, query): 
     42                regses = re.compile(r"(.+)\s+SESSION\s+(.*)", re.I) 
     43                query = regses.sub(r'\1 t_session \2', query) 
     44                return query 
     45 
     46        #method to fix unicode issues and convert longer strings to binary objects 
     47        def _encode_args(self, args): 
     48                if(isinstance(args, list) or isinstance(args,tuple)): 
     49                        for arg in args: 
     50                                if(isinstance(arg, list) or isinstance(arg, tuple)): 
     51                                        yield list(self._encode_args(arg)) 
     52                                elif(isinstance(arg,unicode)): 
     53                                        yield self._check_length(arg.encode('utf-8')) 
     54                                else: 
     55                                        yield self._check_length(arg) 
     56                elif(isinstance(args, unicode)): 
     57                        yield self._check_length(args.encode('utf-8')) 
     58                else: 
     59                        yield self._check_length(args) 
     60         
     61        def _check_length(self, arg): 
     62                if(isinstance(arg, str) == False and isinstance(arg, unicode) == False): 
     63                        return arg 
     64                if len(arg) > 31999: 
     65                        return ingresdbi.Binary(arg) 
     66                else: 
     67                        return arg 
     68                 
     69        def _check_subselect(self, arg): 
     70                reg = re.compile(r"""(^\s*SELECT.+\(\s*SELECT.+)(ORDER BY.*)(\)\s*AS.*)""", re.I | re.M | re.S) 
     71                if re.search(reg, arg): 
     72                        return reg.sub(r'\1\3', arg) 
     73                else: 
     74                        return arg 
     75                 
     76        def execute(self, sql, args = None): 
     77                sql = self._convert_limit(sql) 
     78                sql = self._replace_session(sql) 
     79                sql = self._check_subselect(sql) 
     80                if args: 
     81                        sql = sql.replace('%s', '?') 
     82                        args = list(self._encode_args(args)) 
     83                        return self.cursor.execute(sql, args) 
     84                return self.cursor.execute(sql) 
     85 
     86        def executemany(self, sql, args = None): 
     87                if args: 
     88                        sql = sql.replace('%s', '?') 
     89                        sql = self._convert_limit(sql) 
     90                        sql = self._replace_session(sql) 
     91                        sql = self._check_subselect(sql) 
     92                        args = list(self._encode_args(args)) 
     93                        return self.cursor.executemany(sql, args) 
     94                return self.cursor.execute(sql) 
     95 
     96        def _convert_row(self, row): 
     97                new_row = [] 
     98                for r in row: 
     99                        if isinstance(r, str): 
     100                                new_row.append(r.decode('utf-8')) 
     101                        else: 
     102                                new_row.append(r) 
     103                return new_row   
     104 
     105        def fetchone(self): 
     106                row = self.cursor.fetchone() 
     107                return row and self._convert_row(row) or None 
     108 
     109        def fetchall(self): 
     110                rows = self.cursor.fetchall() 
     111                return rows != None and [self._convert_row(row) for row in rows] or [] 
     112 
     113        def fetchmany(self, num): 
     114                rows = self.cursor.fetchmany(num) 
     115                return rows != None and [self._convert_row(row) for row in rows] or [] 
     116 
     117 
     118class IngresConnector(Component): 
     119        """Ingres backend database support 
     120        usage: ingres://user:password@vnode/database 
     121        """ 
     122         
     123        implements(IDatabaseConnector) 
     124 
     125        def _init_(self): 
     126                self._version = None 
     127                 
     128        #check for whether a field is an index, and convert it to an appropriate data type 
     129        def _is_index(self, name, indices): 
     130                for ind in indices: 
     131                        if name in ind.columns: 
     132                                return True 
     133                        else: 
     134                                return False 
     135         
     136        def get_supported_schemes(self): 
     137                return [('ingres', 1)] 
     138 
     139        def get_connection(self, path, user=None, password=None, host=None, port=None, params={}): 
     140                return IngresConnection(path, user, password, host, port, params) 
     141 
     142        def init_db(self, path, user=None, password=None, host=None, port=None, params={}): 
     143                cnx = self.get_connection(path, user, password, host, port, params) 
     144                cursor = cnx.cursor() 
     145                from trac.db_default import schema 
     146                for table in schema: 
     147                        for stmt in self.to_sql(table): 
     148                                #print stmt 
     149                                cursor.execute(stmt) 
     150                cnx.commit() 
     151 
     152        def to_sql(self, table): 
     153                sql = ['CREATE TABLE %s (' % table.name] 
     154                if table.name == 'session': 
     155                        sql = ['CREATE TABLE t_session ('] 
     156                coldefs = [] 
     157                moddefs = [] 
     158                #find number of text keys 
     159                keys = 0 
     160                for clm in table.columns: 
     161                        if (clm.name in table.key or self._is_index(clm.name, table.indices)) and clm.auto_increment == False: 
     162                                keys += 1 
     163                key_length = 1 
     164                if keys: 
     165                        key_length = 1950 / keys 
     166 
     167                #variable used to determine if there is a procedure to create 
     168                auto_inc = False 
     169                proc = [] 
     170                rule = [] 
     171 
     172                for column in table.columns: 
     173                        ctype = column.type 
     174                        if column.auto_increment: 
     175                                ctype = 'int' #can't use a sequence on text 
     176                                yield "CREATE SEQUENCE %s_%s_seq START WITH 1 INCREMENT BY 1;" % (table.name, column.name) 
     177                                proc.append("CREATE PROCEDURE %s_%s_proc AS BEGIN UPDATE %s SET %s = %s_%s_seq.nextval WHERE %s = 0 END;" % (table.name, column.name, table.name, column.name, table.name, column.name, column.name)) 
     178                                rule.append("CREATE RULE %s_%s_rule AFTER INSERT ON %s EXECUTE PROCEDURE %s_%s_proc;" % (table.name, column.name, table.name, table.name, column.name)) 
     179 
     180                                auto_inc = True 
     181                        if ctype == 'text': 
     182                                if column.name in table.key or self._is_index(column.name, table.indices): 
     183                                        ctype = 'varchar(%s)' % key_length 
     184                                        #path can be really long, make sure it has more room 
     185                                        if table.name == 'node_change': 
     186                                                if column.name == 'path': 
     187                                                        ctype = 'varchar(1700)' 
     188                                                elif column.name == 'rev': 
     189                                                        ctype = 'varchar(256)' 
     190                                                elif column.name == 'change_type': 
     191                                                        ctype = 'varchar(5)' 
     192                                else: 
     193                                        ctype = 'varchar(1024)' 
     194                                if column.name == 'description' and (table.name == 'ticket' or table.name =='milestone'): 
     195                                        ctype = 'long varchar' 
     196                                elif column.name == 'text' and table.name == 'wiki': 
     197                                        ctype = 'long varchar' 
     198                                if table.name == 'enum' and column.name == 'value': 
     199                                        ctype = 'int' 
     200                        if column.auto_increment: 
     201                                default = " WITH DEFAULT 0" 
     202                        else: 
     203                                default = " " 
     204                        """ appends NOT NULL for keys """ 
     205                        if column.name in table.key: 
     206                                coldefs.append("  %s %s NOT NULL%s" % (column.name, ctype,default)) 
     207                        else: 
     208                                coldefs.append("  %s %s%s" % (column.name, ctype, default)) 
     209                         
     210                sql.append(",\n".join(coldefs) + "\n);") 
     211                yield "\n".join(sql) 
     212 
     213                # modify tables to btree instead of heap and sets correct page size and uniqueness. 
     214                if len(table.key) > 0: 
     215                        moddefs.append("MODIFY %s TO BTREE UNIQUE ON %s WITH PAGE_SIZE = 65536;" % (table.name, ",".join(table.key))) 
     216                else: 
     217                        moddefs.append("MODIFY %s TO BTREE WITH PAGE_SIZE = 65536;" % table.name) 
     218 
     219                yield "\n".join(moddefs) 
     220 
     221                #create procedure and rules here, after the table has been created. 
     222                if auto_inc == True: 
     223                        for itm in proc: 
     224                                yield itm 
     225                        for itm in rule: 
     226                                yield itm 
     227 
     228                for index in table.indices: 
     229                        #Use tblname instead of table.name to hack session -> t_sessiona 
     230 
     231                        index_exists = False 
     232                        for col in index.columns: 
     233                                if col in table.key: 
     234                                        index_exists = True 
     235 
     236                        if index_exists: 
     237                                continue 
     238 
     239                        if table.name == 'session': 
     240                                tblname = 't_session' 
     241                        else: 
     242                                tblname = table.name 
     243 
     244                        yield "  CREATE INDEX %s_%s_idx ON %s (%s) with PAGE_SIZE = 65536, STRUCTURE = BTREE;" % (table.name, "_".join(index.columns), tblname, ",".join(index.columns)) 
     245 
     246class IngresConnection(ConnectionWrapper): 
     247        """Ingres Connection Wrapper""" 
     248 
     249        poolable = False 
     250 
     251        def __init__(self, path, user=None, password=None, host=None, port=None, params={}): 
     252         
     253                if path.startswith('/'): 
     254                        path = path[1:] 
     255                if host == None: 
     256                        host = "(local)" 
     257                if user == None: 
     258                        user = '' 
     259                if password == None: 
     260                        password = ''    
     261                cnx = ingresdbi.connect(vnode=host, database=path, uid=user, dbms_pwd=password) 
     262 
     263                ConnectionWrapper.__init__(self, cnx) 
     264                self._is_closed = False 
     265 
     266        def cast(self, column, type): 
     267                return "CAST(%s AS %s)" % (column, type) 
     268 
     269        def concat(self, *args): 
     270                return "concat(%s)" % ",".join(args) 
     271 
     272        def like(self): 
     273                return "LIKE %s ESCAPE '/'" 
     274 
     275        def like_escape(self, text): 
     276                return _like_escape_re.sub(r'/\1', text) 
     277 
     278        def get_last_id(self, cursor, table, column='id'): 
     279                """Hack to get the last inserted ID, will always be max due to procedures.""" 
     280                cursor.execute("SELECT MAX(%s) FROM %s" % (column, table)) 
     281                return cursor.fetchone()[0] 
     282 
     283        def rollback(self): 
     284                self.cnx.rollback() 
     285 
     286        def close(self): 
     287                self.cnx.close() 
     288                self._is_closed = True 
     289 
     290        def cursor(self): 
     291                return IngresCursor(self.cnx.cursor()) 
     292