Ticket #8519: 8519-ilike-r8958.patch
| File 8519-ilike-r8958.patch, 11.4 KB (added by rblank, 2 years ago) |
|---|
-
trac/db/mysql_backend.py
diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py
a b 214 214 return 'concat(%s)' % ', '.join(args) 215 215 216 216 def like(self): 217 return "LIKE %s ESCAPE '/'" 218 219 def ilike(self): 217 220 return "LIKE %s COLLATE utf8_general_ci ESCAPE '/'" 218 221 219 222 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 197 197 return '||'.join(args) 198 198 199 199 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): 202 203 return "ILIKE %s ESCAPE '/'" 203 204 204 205 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 194 194 raise TracError("Backup attempt failed") 195 195 return dest_file 196 196 197 198 def _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 197 234 class SQLiteConnection(ConnectionWrapper): 198 235 """Connection wrapper for SQLite.""" 199 236 … … 226 263 cnx = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES, 227 264 check_same_thread=sqlite_version < 30301, 228 265 timeout=timeout) 266 if sqlite_version >= 30100: 267 cnx.create_function('like', 2, _sqlite_like) 229 268 230 269 ConnectionWrapper.__init__(self, cnx, log) 231 270 … … 253 292 return '||'.join(args) 254 293 255 294 def like(self): 295 return "LIKE %s" 296 297 def ilike(self): 256 298 if sqlite_version >= 30100: 257 299 return "LIKE %s ESCAPE '/'" 258 300 else: -
trac/db/tests/__init__.py
diff --git a/trac/db/tests/__init__.py b/trac/db/tests/__init__.py
a b 1 1 import unittest 2 2 3 3 from trac.db.tests import api 4 from trac.db.tests import postgres_test 4 from trac.db.tests import postgres_test, sqlite_test 5 5 6 6 from trac.db.tests.functional import functionalSuite 7 7 … … 10 10 suite = unittest.TestSuite() 11 11 suite.addTest(api.suite()) 12 12 suite.addTest(postgres_test.suite()) 13 suite.addTest(sqlite_test.suite()) 13 14 return suite 14 15 15 16 if __name__ == '__main__': -
trac/db/tests/api.py
diff --git a/trac/db/tests/api.py b/trac/db/tests/api.py
a b 108 108 self.assertEqual([(u'<em>märkup</em>',)], cursor.fetchall()) 109 109 110 110 111 class 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 111 155 def suite(): 112 156 suite = unittest.TestSuite() 113 157 suite.addTest(unittest.makeSuite(ParseConnectionStringTestCase, 'test')) 114 158 suite.addTest(unittest.makeSuite(StringsTestCase, 'test')) 159 suite.addTest(unittest.makeSuite(LikeTestCase, 'test')) 115 160 return suite 116 161 117 162 if __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 14 import doctest 15 import unittest 16 17 import trac.db.sqlite_backend 18 19 20 def suite(): 21 suite = unittest.TestSuite() 22 suite.addTest(doctest.DocTestSuite(trac.db.sqlite_backend)) 23 return suite 24 25 if __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 47 47 """ 48 48 assert columns and terms 49 49 50 likes = ['%s %s' % (i, db. like()) for i in columns]50 likes = ['%s %s' % (i, db.ilike()) for i in columns] 51 51 c = ' OR '.join(likes) 52 52 sql = '(' + ') AND ('.join([c] * len(terms)) + ')' 53 53 args = [] -
trac/ticket/query.py
diff --git a/trac/ticket/query.py b/trac/ticket/query.py
a b 511 511 if not word: 512 512 continue 513 513 clauses.append("COALESCE(%s,'') %s%s" % (col, cneg, 514 db. like()))514 db.ilike())) 515 515 args.append('%' + db.like_escape(word) + '%') 516 516 if not clauses: 517 517 return None … … 532 532 elif mode == '$': 533 533 value = '%' + value 534 534 return ("COALESCE(%s,'') %s%s" % (col, neg and 'NOT ' or '', 535 db. like()),535 db.ilike()), 536 536 (value, )) 537 537 538 538 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 210 210 FROM ticket AS t 211 211 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 212 212 WHERE ((COALESCE(t.owner,'') %(like)s)) 213 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().like()})213 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 214 214 self.assertEqual(['%someone%'], args) 215 215 tickets = query.execute(self.req) 216 216 … … 222 222 FROM ticket AS t 223 223 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 224 224 WHERE ((COALESCE(t.owner,'') NOT %(like)s)) 225 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().like()})225 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 226 226 self.assertEqual(['%someone%'], args) 227 227 tickets = query.execute(self.req) 228 228 … … 234 234 FROM ticket AS t 235 235 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 236 236 WHERE ((COALESCE(t.owner,'') %(like)s)) 237 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().like()})237 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 238 238 self.assertEqual(['someone%'], args) 239 239 tickets = query.execute(self.req) 240 240 … … 246 246 FROM ticket AS t 247 247 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 248 248 WHERE ((COALESCE(t.owner,'') %(like)s)) 249 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().like()})249 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 250 250 self.assertEqual(['%someone'], args) 251 251 tickets = query.execute(self.req) 252 252 … … 313 313 FROM ticket AS t 314 314 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 315 315 WHERE ((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()})316 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 317 317 tickets = query.execute(self.req) 318 318 319 319 def test_constrained_by_empty_value_contains(self): … … 423 423 FROM ticket AS t 424 424 LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority) 425 425 WHERE (((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()})426 ORDER BY COALESCE(t.id,0)=0,t.id""" % {'like': self.env.get_db_cnx().ilike()}) 427 427 self.assertEqual(['%foo%', '%bar%', '%baz%'], args) 428 428 tickets = query.execute(self.req) 429 429 -
trac/versioncontrol/cache.py
diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
a b 299 299 args.append(path) 300 300 sql += " OR " 301 301 # 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 + '/') + '%') 304 304 sql += " OR " 305 305 # deletion of path ancestors 306 306 components = path.lstrip('/').split('/') 307 for i in range(1, len(components) +1):307 for i in range(1, len(components) + 1): 308 308 args.append('/'.join(components[:i])) 309 309 parent_insert = ','.join(('%s',) * len(components)) 310 310 sql += " (path in (" + parent_insert + ") and change_type='D')"
