Escape underscore char for "contains" query filter

since underscore has special meaning for LIKE clause
This commit is contained in:
Lance Edgar 2024-04-15 12:44:46 -05:00
parent 666c16b74e
commit d0d568b3a5

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar # Copyright © 2010-2024 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -484,9 +484,13 @@ class AlchemyStringFilter(AlchemyGridFilter):
""" """
if value is None or value == '': if value is None or value == '':
return query return query
return query.filter(sa.and_(
*[self.column.ilike(self.encode_value('%{}%'.format(v))) criteria = []
for v in value.split()])) for val in value.split():
val = val.replace('_', r'\_')
val = self.encode_value(f'%{val}%')
criteria.append(self.column.ilike(val))
return query.filter(sa.and_(*criteria))
def filter_does_not_contain(self, query, value): def filter_does_not_contain(self, query, value):
""" """
@ -495,14 +499,17 @@ class AlchemyStringFilter(AlchemyGridFilter):
if value is None or value == '': if value is None or value == '':
return query return query
criteria = []
for val in value.split():
val = val.replace('_', r'\_')
val = self.encode_value(f'%{val}%')
criteria.append(~self.column.ilike(val))
# When saying something is 'not like' something else, we must also # When saying something is 'not like' something else, we must also
# include things which are nothing at all, in our result set. # include things which are nothing at all, in our result set.
return query.filter(sa.or_( return query.filter(sa.or_(
self.column == None, self.column == None,
sa.and_( sa.and_(*criteria)))
*[~self.column.ilike(self.encode_value('%{}%'.format(v)))
for v in value.split()]),
))
def filter_contains_any_of(self, query, value): def filter_contains_any_of(self, query, value):
""" """
@ -531,9 +538,12 @@ class AlchemyStringFilter(AlchemyGridFilter):
conditions = [] conditions = []
for value in values: for value in values:
conditions.append(sa.and_( criteria = []
*[self.column.ilike(self.encode_value('%{}%'.format(v))) for val in value.split():
for v in value.split()])) val = val.replace('_', r'\_')
val = self.encode_value(f'%{val}%')
criteria.append(self.column.ilike(val))
conditions.append(sa.and_(*criteria))
return query.filter(sa.or_(*conditions)) return query.filter(sa.or_(*conditions))
@ -588,8 +598,13 @@ class AlchemyByteStringFilter(AlchemyStringFilter):
""" """
if value is None or value == '': if value is None or value == '':
return query return query
return query.filter(sa.and_(
*[self.column.ilike(b'%{}%'.format(v)) for v in value.split()])) criteria = []
for val in value.split():
val = val.replace('_', r'\_')
val = b'%{}%'.format(val)
criteria.append(self.column.ilike(val))
return query.filters(sa.and_(*criteria))
def filter_does_not_contain(self, query, value): def filter_does_not_contain(self, query, value):
""" """
@ -598,13 +613,16 @@ class AlchemyByteStringFilter(AlchemyStringFilter):
if value is None or value == '': if value is None or value == '':
return query return query
for val in value.split():
val = val.replace('_', '\_')
val = b'%{}%'.format(val)
criteria.append(~self.column.ilike(val))
# When saying something is 'not like' something else, we must also # When saying something is 'not like' something else, we must also
# include things which are nothing at all, in our result set. # include things which are nothing at all, in our result set.
return query.filter(sa.or_( return query.filter(sa.or_(
self.column == None, self.column == None,
sa.and_( sa.and_(*criteria)))
*[~self.column.ilike(b'%{}%'.format(v)) for v in value.split()]),
))
class AlchemyNumericFilter(AlchemyGridFilter): class AlchemyNumericFilter(AlchemyGridFilter):