diff --git a/API.md b/API.md
index ecc9d06c..0744c91f 100644
--- a/API.md
+++ b/API.md
@@ -117,7 +117,7 @@ data.
| `
<year>-<month>-<day>
Some fields, such as user names, can take wildcards (*
).
All tokens can be negated by prepending them with -
.
Order token values can be appended with ,asc
or
diff --git a/server/szurubooru/search/base_search_config.py b/server/szurubooru/search/base_search_config.py
index 0e6712f6..8de0a234 100644
--- a/server/szurubooru/search/base_search_config.py
+++ b/server/szurubooru/search/base_search_config.py
@@ -3,32 +3,19 @@ import szurubooru.errors
from szurubooru.util import misc
from szurubooru.search import criteria
-def _apply_criterion_to_column(
- column, query, criterion, allow_composite=True, allow_ranged=True):
+def _apply_num_criterion_to_column(column, query, criterion):
''' Decorate SQLAlchemy filter on given column using supplied criterion. '''
if isinstance(criterion, criteria.StringSearchCriterion):
expr = column == criterion.value
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
elif isinstance(criterion, criteria.ArraySearchCriterion):
- if not allow_composite:
- raise szurubooru.errors.SearchError(
- 'Composite token %r is invalid in this context.' % (criterion,))
expr = column.in_(criterion.values)
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
elif isinstance(criterion, criteria.RangedSearchCriterion):
- if not allow_ranged:
- raise szurubooru.errors.SearchError(
- 'Ranged token %r is invalid in this context.' % (criterion,))
expr = column.between(criterion.min_value, criterion.max_value)
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
else:
- raise RuntimeError('Invalid search type: %r.' % (criterion,))
+ assert False
+ if criterion.negated:
+ expr = ~expr
+ return query.filter(expr)
def _apply_date_criterion_to_column(column, query, criterion):
'''
@@ -38,17 +25,11 @@ def _apply_date_criterion_to_column(column, query, criterion):
if isinstance(criterion, criteria.StringSearchCriterion):
min_date, max_date = misc.parse_time_range(criterion.value)
expr = column.between(min_date, max_date)
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = sqlalchemy.sql.false()
for value in criterion.values:
min_date, max_date = misc.parse_time_range(value)
expr = expr | column.between(min_date, max_date)
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
elif isinstance(criterion, criteria.RangedSearchCriterion):
assert criterion.min_value or criterion.max_value
if criterion.min_value and criterion.max_value:
@@ -61,9 +42,31 @@ def _apply_date_criterion_to_column(column, query, criterion):
elif criterion.max_value:
max_date = misc.parse_time_range(criterion.max_value)[1]
expr = column <= max_date
- if criterion.negated:
- expr = ~expr
- return query.filter(expr)
+ else:
+ assert False
+ if criterion.negated:
+ expr = ~expr
+ return query.filter(expr)
+
+def _apply_str_criterion_to_column(column, query, criterion):
+ '''
+ Decorate SQLAlchemy filter on given column using supplied criterion.
+ Parse potential wildcards inside the criterion.
+ '''
+ if isinstance(criterion, criteria.StringSearchCriterion):
+ expr = column.like(criterion.value.replace('*', '%'))
+ elif isinstance(criterion, criteria.ArraySearchCriterion):
+ expr = sqlalchemy.sql.false()
+ for value in criterion.values:
+ expr = expr | column.like(value.replace('*', '%'))
+ elif isinstance(criterion, criteria.RangedSearchCriterion):
+ raise szurubooru.errors.SearchError(
+ 'Composite token %r is invalid in this context.' % (criterion,))
+ else:
+ assert False
+ if criterion.negated:
+ expr = ~expr
+ return query.filter(expr)
class BaseSearchConfig(object):
def create_query(self, session):
@@ -85,11 +88,14 @@ class BaseSearchConfig(object):
def order_columns(self):
raise NotImplementedError()
- def _create_basic_filter(
- self, column, allow_composite=True, allow_ranged=True):
- return lambda query, criterion: _apply_criterion_to_column(
- column, query, criterion, allow_composite, allow_ranged)
+ def _create_num_filter(self, column):
+ return lambda query, criterion: _apply_num_criterion_to_column(
+ column, query, criterion)
def _create_date_filter(self, column):
return lambda query, criterion: _apply_date_criterion_to_column(
column, query, criterion)
+
+ def _create_str_filter(self, column):
+ return lambda query, criterion: _apply_str_criterion_to_column(
+ column, query, criterion)
diff --git a/server/szurubooru/search/user_search_config.py b/server/szurubooru/search/user_search_config.py
index 589e1d59..45796f10 100644
--- a/server/szurubooru/search/user_search_config.py
+++ b/server/szurubooru/search/user_search_config.py
@@ -13,7 +13,7 @@ class UserSearchConfig(BaseSearchConfig):
@property
def anonymous_filter(self):
- return self._create_basic_filter(db.User.name, allow_ranged=False)
+ return self._create_str_filter(db.User.name)
@property
def special_filters(self):
@@ -22,7 +22,7 @@ class UserSearchConfig(BaseSearchConfig):
@property
def named_filters(self):
return {
- 'name': self._create_basic_filter(db.User.name, allow_ranged=False),
+ 'name': self._create_str_filter(db.User.name),
'creation-date': self._create_date_filter(db.User.creation_time),
'creation-time': self._create_date_filter(db.User.creation_time),
'last-login-date': self._create_date_filter(db.User.last_login_time),
diff --git a/server/szurubooru/tests/search/test_user_search_config.py b/server/szurubooru/tests/search/test_user_search_config.py
index 0cc6c1d5..c151cd55 100644
--- a/server/szurubooru/tests/search/test_user_search_config.py
+++ b/server/szurubooru/tests/search/test_user_search_config.py
@@ -96,6 +96,18 @@ class TestUserSearchExecutor(DatabaseTestCase):
self._test('name:u1', 1, 100, 1, ['u1'])
self._test('name:u2', 1, 100, 1, ['u2'])
+ def test_filter_by_name_wildcards(self):
+ self.session.add(util.mock_user('user1'))
+ self.session.add(util.mock_user('user2'))
+ self._test('name:*1', 1, 100, 1, ['user1'])
+ self._test('name:*2', 1, 100, 1, ['user2'])
+ self._test('name:*', 1, 100, 2, ['user1', 'user2'])
+ self._test('name:u*', 1, 100, 2, ['user1', 'user2'])
+ self._test('name:*ser*', 1, 100, 2, ['user1', 'user2'])
+ self._test('name:*zer*', 1, 100, 0, [])
+ self._test('name:zer*', 1, 100, 0, [])
+ self._test('name:*zer', 1, 100, 0, [])
+
def test_filter_by_negated_name(self):
self.session.add(util.mock_user('u1'))
self.session.add(util.mock_user('u2'))