Edgewall Software

Ticket #8519: t8519-case-sensitive-like-r10949.diff

File t8519-case-sensitive-like-r10949.diff, 9.2 KB (added by jomae, 4 months ago)

Supports case-sensitive db.like() and adds db.like_pattern() helper.

  • trac/ticket/model.py

     
    533533            cursor.execute(""" 
    534534                SELECT field FROM ticket_change  
    535535                WHERE ticket=%%s AND time=%%s AND field %s 
    536                 """ % db.like(), (self.id, ts, 
    537                                   db.like_escape('_comment') + '%')) 
     536                """ % db.like(ignore_case=False), 
     537                (self.id, ts, db.like_pattern(['_comment', db.ANY_CHARS], 
     538                                              ignore_case=False))) 
    538539            fields = list(cursor) 
    539540            rev = fields and max(int(field[8:]) for field, in fields) + 1 or 0 
    540541            cursor.execute(""" 
     
    549550                cursor.execute(""" 
    550551                    SELECT author FROM ticket_change  
    551552                    WHERE ticket=%%s AND time=%%s AND NOT field %s 
    552                     LIMIT 1 
    553                     """ % db.like(), (self.id, ts, db.like_escape('_') + '%')) 
     553                    LIMIT 1""" % db.like(ignore_case=False), 
     554                    (self.id, ts, db.like_pattern(['_', db.ANY_CHARS], 
     555                                                  ignore_case=False))) 
    554556                old_author = None 
    555557                for old_author, in cursor: 
    556558                    break 
     
    584586                SELECT field,author,oldvalue,newvalue  
    585587                FROM ticket_change  
    586588                WHERE ticket=%%s AND time=%%s AND field %s 
    587                 """ % db.like(), (self.id, ts0, 
    588                                   db.like_escape('_comment') + '%')) 
     589                """ % db.like(ignore_case=False), 
     590                (self.id, ts0, db.like_pattern(['_comment', db.ANY_CHARS], 
     591                                               ignore_case=False))) 
    589592            rows = sorted((int(field[8:]), author, old, new) 
    590593                          for field, author, old, new in cursor) 
    591594            for rev, author, comment, ts in rows: 
     
    606609            SELECT time,author,newvalue FROM ticket_change  
    607610            WHERE ticket=%%s AND field='comment'  
    608611                AND (oldvalue=%%s OR oldvalue %s) 
    609             """ % db.like(), (self.id, scnum, 
    610                               '%' + db.like_escape('.' + scnum))) 
     612            """ % db.like(ignore_case=False), 
     613            (self.id, scnum, db.like_pattern([db.ANY_CHARS, '.' + scnum], 
     614                                             ignore_case=False))) 
    611615        for row in cursor: 
    612616            return row 
    613617 
     
    638642                SELECT author FROM ticket_change  
    639643                WHERE ticket=%%s AND time=%%s AND NOT field %s  
    640644                LIMIT 1 
    641                 """ % db.like(), (self.id, ts, db.like_escape('_') + '%')) 
     645                """ % db.like(ignore_case=False), 
     646                (self.id, ts, db.like_pattern(['_', db.ANY_CHARS], 
     647                                              ignore_case=False))) 
    642648            for author, in cursor: 
    643649                break 
    644650        return (ts, author, comment) 
  • trac/versioncontrol/cache.py

     
    325325        sfirst = self.db_rev(first) 
    326326        cursor.execute("SELECT DISTINCT rev FROM node_change " 
    327327                       "WHERE repos=%%s AND rev>=%%s AND rev<=%%s " 
    328                        " AND (path=%%s OR path %s)" % db.like(), 
     328                       " AND (path=%%s OR path %s)" % \ 
     329                       db.like(ignore_case=False), 
    329330                       (self.id, sfirst, slast, path, 
    330                         db.like_escape(path + '/') + '%')) 
     331                        db.like_pattern([path + '/', db.ANY_CHARS], 
     332                                        ignore_case=False))) 
    331333        return [int(row[0]) for row in cursor] 
    332334 
    333335    def has_node(self, path, rev=None): 
     
    362364        if path: 
    363365            path = path.lstrip('/') 
    364366            # changes on path itself or its children 
    365             sql += " AND (path=%s OR path " + db.like() 
    366             args.extend((path, db.like_escape(path + '/') + '%')) 
     367            sql += " AND (path=%s OR path " + db.like(ignore_case=False) 
     368            args.extend((path, db.like_pattern([path + '/', db.ANY_CHARS], 
     369                                               ignore_case=False))) 
    367370            # deletion of path ancestors 
    368371            components = path.lstrip('/').split('/') 
    369372            parents = ','.join(('%s',) * len(components)) 
  • trac/db/sqlite_backend.py

     
    2626from trac.util.translation import _ 
    2727 
    2828_like_escape_re = re.compile(r'([/_%])') 
     29_glob_escape_re = re.compile(r'[*?[]') 
    2930 
    3031try: 
    3132    import pysqlite2.dbapi2 as sqlite 
     
    297298    def concat(self, *args): 
    298299        return '||'.join(args) 
    299300 
    300     def like(self): 
    301         """Return a case-insensitive LIKE clause.""" 
    302         if sqlite_version >= (3, 1, 0): 
    303             return "LIKE %s ESCAPE '/'" 
     301    def like(self, ignore_case=True): 
     302        """Return a LIKE clause.""" 
     303        if ignore_case: 
     304            if sqlite_version >= (3, 1, 0): 
     305                return "LIKE %s ESCAPE '/'" 
     306            else: 
     307                return 'LIKE %s' 
    304308        else: 
    305             return 'LIKE %s' 
     309            return 'GLOB %s' 
    306310 
    307     def like_escape(self, text): 
    308         if sqlite_version >= (3, 1, 0): 
    309             return _like_escape_re.sub(r'/\1', text) 
     311    def like_escape(self, text, ignore_case=True): 
     312        if ignore_case: 
     313            if sqlite_version >= (3, 1, 0): 
     314                return _like_escape_re.sub(r'/\1', text) 
     315            else: 
     316                return text 
    310317        else: 
    311             return text 
     318            return _glob_escape_re.sub(r'[\g<0>]', text) 
    312319 
     320    def like_pattern(self, texts, ignore_case=True): 
     321        """Return a pattern for LIKE operator.""" 
     322        if ignore_case: 
     323            any_chars = '%' 
     324            single_char = '_' 
     325        else: 
     326            any_chars = '*' 
     327            single_char = '?' 
     328 
     329        pattern = [] 
     330        if not isinstance(texts, (list, tuple)): 
     331            texts = [texts] 
     332        for text in texts: 
     333            if text is self.ANY_CHARS: 
     334                text = any_chars 
     335            elif text is self.SINGLE_CHAR: 
     336                text = single_char 
     337            else: 
     338                text = self.like_escape(text, ignore_case=ignore_case) 
     339            pattern.append(text) 
     340        return ''.join(pattern) 
     341 
    313342    def quote(self, identifier): 
    314343        """Return the quoted identifier.""" 
    315344        return "`%s`" % identifier.replace('`', '``') 
  • trac/db/mysql_backend.py

     
    242242    def concat(self, *args): 
    243243        return 'concat(%s)' % ', '.join(args) 
    244244 
    245     def like(self): 
    246         """Return a case-insensitive LIKE clause.""" 
    247         return "LIKE %s COLLATE utf8_general_ci ESCAPE '/'" 
     245    def like(self, ignore_case=True): 
     246        """Return a LIKE clause.""" 
     247        if ignore_case: 
     248            return "LIKE %s COLLATE utf8_general_ci ESCAPE '/'" 
     249        else: 
     250            return "LIKE %s ESCAPE '/'" 
    248251 
    249     def like_escape(self, text): 
     252    def like_escape(self, text, ignore_case=True): 
    250253        return _like_escape_re.sub(r'/\1', text) 
    251254 
    252255    def quote(self, identifier): 
  • trac/db/postgres_backend.py

     
    234234    def concat(self, *args): 
    235235        return '||'.join(args) 
    236236 
    237     def like(self): 
    238         """Return a case-insensitive LIKE clause.""" 
    239         return "ILIKE %s ESCAPE '/'" 
     237    def like(self, ignore_case=True): 
     238        """Return a LIKE clause.""" 
     239        if ignore_case: 
     240            return "ILIKE %s ESCAPE '/'" 
     241        else: 
     242            return "LIKE %s ESCAPE '/'" 
    240243 
    241     def like_escape(self, text): 
     244    def like_escape(self, text, ignore_case=True): 
    242245        return _like_escape_re.sub(r'/\1', text) 
    243246 
    244247    def quote(self, identifier): 
  • trac/db/util.py

     
    9494    """ 
    9595    __slots__ = ('cnx', 'log') 
    9696 
     97    ANY_CHARS = object() 
     98    SINGLE_CHAR = object() 
     99 
    97100    def __init__(self, cnx, log=None): 
    98101        self.cnx = cnx 
    99102        self.log = log 
    100103 
    101104    def __getattr__(self, name): 
    102105        return getattr(self.cnx, name) 
     106 
     107    def like_pattern(self, texts, ignore_case=True): 
     108        pattern = [] 
     109        if not isinstance(texts, (list, tuple)): 
     110            texts = [texts] 
     111        for text in texts: 
     112            if text is self.ANY_CHARS: 
     113                text = '%' 
     114            elif text is self.SINGLE_CHAR: 
     115                text = '_' 
     116            else: 
     117                text = self.like_escape(text, ignore_case=ignore_case) 
     118            pattern.append(text) 
     119        return ''.join(pattern)