@@ -240,10 +240,16 @@ def sql_fetchiter(self):
240240 if not row : break
241241 yield row
242242
243- def sql_stringquote (self , value ):
244- """ Quote the string so it's safe to put in the 'sql quotes'
243+ def search_stringquote (self , value ):
244+ """ Quote a search string to escape magic search characters
245+ '%' and '_', also need to quote '\' (first)
246+ Then put '%' around resulting string for LIKE (or ILIKE) operator
245247 """
246- return re .sub ("'" , "''" , str (value ))
248+ v = value .replace ('\\ ' , '\\ \\ ' )
249+ v = v .replace ('%' , '\\ %' )
250+ v = v .replace ('_' , '\\ _' )
251+ return '%' + v + '%'
252+
247253
248254 def init_dbschema (self ):
249255 self .database_schema = {
@@ -1463,6 +1469,10 @@ class Class(hyperdb.Class):
14631469 All methods except __repr__ and getnode must be implemented by a
14641470 concrete backend Class.
14651471 """
1472+ # For many databases the LIKE operator ignores case.
1473+ # Postgres and Oracle have an ILIKE operator to support this.
1474+ # We define the default here, can be changed in derivative class
1475+ case_insensitive_like = 'LIKE'
14661476
14671477 def schema (self ):
14681478 """ A dumpable version of the schema that we can store in the
@@ -2433,23 +2443,24 @@ def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0):
24332443 if not isinstance (v , type ([])):
24342444 v = [v ]
24352445
2436- # Quote the bits in the string that need it and then embed
2437- # in a "substring" search. Note - need to quote the '%' so
2438- # they make it through the python layer happily
2439- v = ['%%' + self .db .sql_stringquote (s )+ '%%' for s in v ]
2446+ # Quote special search characters '%' and '_' for
2447+ # correct matching with LIKE/ILIKE
2448+ # Note that we now pass the elements of v as query
2449+ # arguments and don't interpolate the quoted string
2450+ # into the sql statement. Should be safer.
2451+ v = [self .db .search_stringquote (s ) for s in v ]
24402452
24412453 # now add to the where clause
24422454 where .append ('('
2443- + ' and ' .join (["_%s._%s %s '%s' " % (
2455+ + ' and ' .join (["_%s._%s %s %s ESCAPE %s " % (
24442456 pln ,
24452457 k ,
2446- # For many databases the LIKE operator
2447- # ignores case. Postgres and Oracle have
2448- # an ILIKE operator to support this.
2449- getattr (self ,'case_insensitive_like' ,'LIKE' ),
2450- s ) for s in v ])
2458+ self .case_insensitive_like ,
2459+ a ,
2460+ a ) for s in v ])
24512461 + ')' )
2452- # note: args are embedded in the query string now
2462+ for vv in v :
2463+ args .extend ((vv , '\\ ' ))
24532464 if 'sort' in p .need_for :
24542465 oc = ac = 'lower(_%s._%s)' % (pln , k )
24552466 elif isinstance (propclass , Link ):
0 commit comments