From b7b298c7277455a125e6ae8b059522c242238535 Mon Sep 17 00:00:00 2001 From: John Kristensen Date: Tue, 10 Feb 2015 14:12:37 +1100 Subject: [PATCH] Fix filtering special characters in some MySQL environments (issue2550868) MySQL servers configured with 'collation_server = utf8_unicode_ci' do not behave the same way when escaping characters as when this configuration is not set - an extra escape character is required when using 'utf8_unicode_ci'. ie. ... LIKE '%\\\\\%' ESCAPE '\\' ... instead of ... LIKE '%\\\\%' ESCAPE '\\' ... (see: http://bugs.mysql.com/bug.php?id=74901) To work around the issue attempt to search for an escape character that is not used in the search filter and use that character. If no unused escape character can be found, default back to the old behaviour which should still work when the 'collation_server' is not set to 'utf8_unicode_ci' --- roundup/backends/rdbms_common.py | 26 ++++++++++++++++++-------- test/db_test_base.py | 8 ++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index b30380537c..ba14d6d1b5 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -242,14 +242,24 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): def search_stringquote(self, value): """ Quote a search string to escape magic search characters - '%' and '_', also need to quote '\' (first) + '%' and '_', also need to quote '\\' (first) Then put '%' around resulting string for LIKE (or ILIKE) operator """ - v = value.replace('\\', '\\\\') - v = v.replace('%', '\\%') - v = v.replace('_', '\\_') - return '%' + v + '%' + replace_chars = ['%', '_'] + for char in '!#$&*+,-./:;@^|~': + if char not in value: + escape_char = char + break + else: + escape_char = '\\' + replace_chars += ['\\'] + + v = value + for char in replace_chars: + v = v.replace(char, escape_char + char) + + return ('%' + v + '%', escape_char) def init_dbschema(self): self.database_schema = { @@ -2457,10 +2467,10 @@ class Class(hyperdb.Class): k, self.case_insensitive_like, a, - a) for s in v]) + a) for s, e in v]) +')') - for vv in v: - args.extend((vv, '\\')) + for escape in v: + args.extend(escape) if 'sort' in p.need_for: oc = ac = 'lower(_%s._%s)'%(pln, k) elif isinstance(propclass, Link): diff --git a/test/db_test_base.py b/test/db_test_base.py index 04f534d8be..c988aab352 100644 --- a/test/db_test_base.py +++ b/test/db_test_base.py @@ -1307,6 +1307,14 @@ class DBTest(commonDBTest): ae(filt(None, dict(title='\\'), ('+','id'), (None,None)), ['3']) ae(filt(None, dict(title="'"), ('+','id'), (None,None)), ['4']) + self.db.issue.set('1', title="With !# symbol") + self.db.issue.set('2', title="With \\!#$&*+,-./:;@^|~ symbol") + for filt in filter, filter_iter: + ae(filt(None, dict(title='!#'), ('+', 'id'), (None, None)), + ['1', '2']) + ae(filt(None, dict(title='!#$&*+,-./:;@^|~'), ('+', 'id')), + ['2']) + def testFilteringLink(self): ae, filter, filter_iter = self.filteringSetup() a = 'assignedto' -- 2.1.4