Edgewall Software

Ticket #8519: 8519-ilike-r8958.patch

File 8519-ilike-r8958.patch, 11.4 KB (added by rblank, 2 years ago)

Add ilike() to the DB connection, and implement a correct like() for PostgreSQL and MySQL. Misuse the LIKE operator on SQLite.

  • trac/db/mysql_backend.py

    diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py
    a b  
    214214        return 'concat(%s)' % ', '.join(args) 
    215215 
    216216    def like(self): 
     217        return "LIKE %s ESCAPE '/'" 
     218 
     219    def ilike(self): 
    217220        return "LIKE %s COLLATE utf8_general_ci ESCAPE '/'" 
    218221 
    219222    def like_escape(self, text): 
  • trac/db/postgres_backend.py

    diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py
    a b  
    197197        return '||'.join(args) 
    198198 
    199199    def like(self): 
    200         # Temporary hack needed for the case-insensitive string matching in the 
    201         # search module 
     200        return "LIKE %s ESCAPE '/'" 
     201 
     202    def ilike(self): 
    202203        return "ILIKE %s ESCAPE '/'" 
    203204 
    204205    def like_escape(self, text): 
  • trac/db/sqlite_backend.py

    diff --git a/trac/db/sqlite_backend.py b/trac/db/sqlite_backend.py
    a b  
    194194            raise TracError("Backup attempt failed") 
    195195        return dest_file 
    196196 
     197 
     198def _sqlite_like(p, s): 
     199    """Function to be used as `s LIKE p` operator in SQLite statements. 
     200     
     201    >>> _sqlite_like('Starts with%', 'Starts with this') 
     202    True 
     203    >>> _sqlite_like('Starts with%', ' Starts with this') 
     204    False 
     205    >>> _sqlite_like('%ends with', '... ends with') 
     206    True 
     207    >>> _sqlite_like('%ends with', ' ends with ...') 
     208    False 
     209    >>> _sqlite_like('%contains%', 'This contains the pattern') 
     210    True 
     211    >>> _sqlite_like('%contains%', "This doesn't contain the pattern") 
     212    False 
     213    >>> _sqlite_like('This is the pattern', 'This is the pattern') 
     214    True 
     215    >>> _sqlite_like('This is the pattern', 'This is not the pattern') 
     216    False 
     217    >>> _sqlite_like('', 'Anything') 
     218    True 
     219    """ 
     220    if not p: 
     221        return True 
     222    l = p[-1] == '%' 
     223    if p[0] == '%': 
     224        if l: 
     225            return p[1:-1] in s 
     226        else: 
     227            return s.endswith(p[1:]) 
     228    elif l: 
     229        return s.startswith(p[:-1]) 
     230    else: 
     231        return s == p 
     232 
     233 
    197234class SQLiteConnection(ConnectionWrapper): 
    198235    """Connection wrapper for SQLite.""" 
    199236 
     
    226263        cnx = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES, 
    227264                             check_same_thread=sqlite_version < 30301, 
    228265                             timeout=timeout) 
     266        if sqlite_version >= 30100: 
     267            cnx.create_function('like', 2, _sqlite_like) 
    229268             
    230269        ConnectionWrapper.__init__(self, cnx, log) 
    231270 
     
    253292        return '||'.join(args) 
    254293 
    255294    def like(self): 
     295        return "LIKE %s" 
     296 
     297    def ilike(self): 
    256298        if sqlite_version >= 30100: 
    257299            return "LIKE %s ESCAPE '/'" 
    258300        else: 
  • trac/db/tests/__init__.py

    diff --git a/trac/db/tests/__init__.py b/trac/db/tests/__init__.py
    a b  
    11import unittest 
    22 
    33from trac.db.tests import api 
    4 from trac.db.tests import postgres_test 
     4from trac.db.tests import postgres_test, sqlite_test 
    55 
    66from trac.db.tests.functional import functionalSuite 
    77 
     
    1010    suite = unittest.TestSuite() 
    1111    suite.addTest(api.suite()) 
    1212    suite.addTest(postgres_test.suite()) 
     13    suite.addTest(sqlite_test.suite()) 
    1314    return suite 
    1415 
    1516if __name__ == '__main__': 
  • trac/db/tests/api.py

    diff --git a/trac/db/tests/api.py b/trac/db/tests/api.py
    a b  
    108108        self.assertEqual([(u'<em>märkup</em>',)], cursor.fetchall()) 
    109109 
    110110 
     111class LikeTestCase(unittest.TestCase): 
     112     
     113    def setUp(self): 
     114        self.env = EnvironmentStub() 
     115     
     116    def test_like(self): 
     117        db = self.env.get_db_cnx() 
     118        cursor = db.cursor() 
     119        cursor.executemany("INSERT INTO system (name,value) VALUES (%s,%s)", 
     120                           [('name', 'value'), ('NAME', 'value')]) 
     121        db.commit() 
     122        cursor.execute("SELECT name FROM system WHERE name %s" % db.like(), 
     123                       ('name',)) 
     124        self.assertEqual(1, len(list(cursor))) 
     125        cursor.execute("SELECT name FROM system WHERE name %s" % db.like(), 
     126                       ('na%',)) 
     127        self.assertEqual(1, len(list(cursor))) 
     128        cursor.execute("SELECT name FROM system WHERE name %s" % db.like(), 
     129                       ('%me',)) 
     130        self.assertEqual(1, len(list(cursor))) 
     131        cursor.execute("SELECT name FROM system WHERE name %s" % db.like(), 
     132                       ('%am%',)) 
     133        self.assertEqual(1, len(list(cursor))) 
     134 
     135    def test_ilike(self): 
     136        db = self.env.get_db_cnx() 
     137        cursor = db.cursor() 
     138        cursor.executemany("INSERT INTO system (name,value) VALUES (%s,%s)", 
     139                           [('like', 'value'), ('LIKE', 'value')]) 
     140        db.commit() 
     141        cursor.execute("SELECT name FROM system WHERE name %s" % db.ilike(), 
     142                       ('like',)) 
     143        self.assertEqual(2, len(list(cursor))) 
     144        cursor.execute("SELECT name FROM system WHERE name %s" % db.ilike(), 
     145                       ('li%',)) 
     146        self.assertEqual(2, len(list(cursor))) 
     147        cursor.execute("SELECT name FROM system WHERE name %s" % db.ilike(), 
     148                       ('%ke',)) 
     149        self.assertEqual(2, len(list(cursor))) 
     150        cursor.execute("SELECT name FROM system WHERE name %s" % db.ilike(), 
     151                       ('%ik%',)) 
     152        self.assertEqual(2, len(list(cursor))) 
     153 
     154 
    111155def suite(): 
    112156    suite = unittest.TestSuite() 
    113157    suite.addTest(unittest.makeSuite(ParseConnectionStringTestCase, 'test')) 
    114158    suite.addTest(unittest.makeSuite(StringsTestCase, 'test')) 
     159    suite.addTest(unittest.makeSuite(LikeTestCase, 'test')) 
    115160    return suite 
    116161 
    117162if __name__ == '__main__': 
  • new file trac/db/tests/sqlite_test.py

    diff --git a/trac/db/tests/sqlite_test.py b/trac/db/tests/sqlite_test.py
    new file mode 100644
    - +  
     1# -*- coding: utf-8 -*- 
     2# 
     3# Copyright (C) 2009 Edgewall Software 
     4# All rights reserved. 
     5# 
     6# This software is licensed as described in the file COPYING, which 
     7# you should have received as part of this distribution. The terms 
     8# are also available at http://trac.edgewall.org/wiki/TracLicense. 
     9# 
     10# This software consists of voluntary contributions made by many 
     11# individuals. For the exact contribution history, see the revision 
     12# history and logs, available at http://trac.edgewall.org/log/. 
     13 
     14import doctest 
     15import unittest 
     16 
     17import trac.db.sqlite_backend 
     18 
     19 
     20def suite(): 
     21    suite = unittest.TestSuite() 
     22    suite.addTest(doctest.DocTestSuite(trac.db.sqlite_backend)) 
     23    return suite 
     24 
     25if __name__ == '__main__': 
     26    unittest.main(defaultTest='suite') 
  • trac/search/api.py

    diff --git a/trac/search/api.py b/trac/search/api.py
    a b  
    4747    """ 
    4848    assert columns and terms 
    4949 
    50     likes = ['%s %s' % (i, db.like()) for i in columns] 
     50    likes = ['%s %s' % (i, db.ilike()) for i in columns] 
    5151    c = ' OR '.join(likes) 
    5252    sql = '(' + ') AND ('.join([c] * len(terms)) + ')' 
    5353    args = [] 
  • trac/ticket/query.py

    diff --git a/trac/ticket/query.py b/trac/ticket/query.py
    a b  
    511511                        if not word: 
    512512                            continue 
    513513                    clauses.append("COALESCE(%s,'') %s%s" % (col, cneg, 
    514                                                              db.like())) 
     514                                                             db.ilike())) 
    515515                    args.append('%' + db.like_escape(word) + '%') 
    516516                if not clauses: 
    517517                    return None 
     
    532532            elif mode == '$': 
    533533                value = '%' + value 
    534534            return ("COALESCE(%s,'') %s%s" % (col, neg and 'NOT ' or '', 
    535                                               db.like()), 
     535                                              db.ilike()), 
    536536                    (value, )) 
    537537 
    538538        def get_clause_sql(constraints): 
  • trac/ticket/tests/query.py

    diff --git a/trac/ticket/tests/query.py b/trac/ticket/tests/query.py
    a b  
    210210FROM ticket AS t 
    211211  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    212212WHERE ((COALESCE(t.owner,'') %(like)s)) 
    213 ORDER BY COALESCE(t.id,0)=0,t.id""" %  {'like': self.env.get_db_cnx().like()}) 
     213ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    214214        self.assertEqual(['%someone%'], args) 
    215215        tickets = query.execute(self.req) 
    216216 
     
    222222FROM ticket AS t 
    223223  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    224224WHERE ((COALESCE(t.owner,'') NOT %(like)s)) 
    225 ORDER BY COALESCE(t.id,0)=0,t.id""" %  {'like': self.env.get_db_cnx().like()}) 
     225ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    226226        self.assertEqual(['%someone%'], args) 
    227227        tickets = query.execute(self.req) 
    228228 
     
    234234FROM ticket AS t 
    235235  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    236236WHERE ((COALESCE(t.owner,'') %(like)s)) 
    237 ORDER BY COALESCE(t.id,0)=0,t.id""" %  {'like': self.env.get_db_cnx().like()}) 
     237ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    238238        self.assertEqual(['someone%'], args) 
    239239        tickets = query.execute(self.req) 
    240240 
     
    246246FROM ticket AS t 
    247247  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    248248WHERE ((COALESCE(t.owner,'') %(like)s)) 
    249 ORDER BY COALESCE(t.id,0)=0,t.id""" %  {'like': self.env.get_db_cnx().like()}) 
     249ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    250250        self.assertEqual(['%someone'], args) 
    251251        tickets = query.execute(self.req) 
    252252 
     
    313313FROM ticket AS t 
    314314  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    315315WHERE ((COALESCE(t.owner,'') %(like)s OR COALESCE(t.owner,'') %(like)s)) 
    316 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().like()}) 
     316ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    317317        tickets = query.execute(self.req) 
    318318 
    319319    def test_constrained_by_empty_value_contains(self): 
     
    423423FROM ticket AS t 
    424424  LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 
    425425WHERE (((COALESCE(t.keywords,'') %(like)s AND COALESCE(t.keywords,'') NOT %(like)s AND COALESCE(t.keywords,'') %(like)s))) 
    426 ORDER BY COALESCE(t.id,0)=0,t.id"""  % {'like': self.env.get_db_cnx().like()}) 
     426ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 
    427427        self.assertEqual(['%foo%', '%bar%', '%baz%'], args) 
    428428        tickets = query.execute(self.req) 
    429429 
  • trac/versioncontrol/cache.py

    diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
    a b  
    299299            args.append(path) 
    300300            sql += " OR " 
    301301            # changes on path children 
    302             sql += "path "+db.like() 
    303             args.append(db.like_escape(path+'/') + '%') 
     302            sql += "path " + db.like() 
     303            args.append(db.like_escape(path + '/') + '%') 
    304304            sql += " OR " 
    305305            # deletion of path ancestors 
    306306            components = path.lstrip('/').split('/') 
    307             for i in range(1, len(components)+1): 
     307            for i in range(1, len(components) + 1): 
    308308                args.append('/'.join(components[:i])) 
    309309            parent_insert = ','.join(('%s',) * len(components)) 
    310310            sql += " (path in (" + parent_insert + ") and change_type='D')"