Skip to content

Commit 8c3e2b3

Browse files
authored
Merge pull request #2568 from idoshr/regex_query
Regex and whole word text search query
2 parents 0af1a11 + 7a0a58c commit 8c3e2b3

File tree

4 files changed

+54
-13
lines changed

4 files changed

+54
-13
lines changed

docs/guide/querying.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ expressions:
8686
* ``istartswith`` -- string field starts with value (case insensitive)
8787
* ``endswith`` -- string field ends with value
8888
* ``iendswith`` -- string field ends with value (case insensitive)
89+
* ``wholeword`` -- string field contains whole word
90+
* ``iwholeword`` -- string field contains whole word (case insensitive)
91+
* ``regex`` -- string field match by regex
92+
* ``iregex`` -- string field match by regex (case insensitive)
8993
* ``match`` -- performs an $elemMatch so you can match an entire document within an array
9094

9195

mongoengine/fields.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,17 @@ def prepare_query_value(self, op, value):
157157
regex = r"%s$"
158158
elif op == "exact":
159159
regex = r"^%s$"
160+
elif op == "wholeword":
161+
regex = r"\b%s\b"
162+
elif op == "regex":
163+
regex = value
160164

161165
# escape unsafe characters which could lead to a re.error
162-
value = re.escape(value)
163-
value = re.compile(regex % value, flags)
166+
if op == "regex":
167+
value = re.compile(regex, flags)
168+
else:
169+
value = re.escape(value)
170+
value = re.compile(regex % value, flags)
164171
return super().prepare_query_value(op, value)
165172

166173

@@ -1086,16 +1093,7 @@ def lookup_member(self, member_name):
10861093
return DictField(db_field=member_name)
10871094

10881095
def prepare_query_value(self, op, value):
1089-
match_operators = [
1090-
"contains",
1091-
"icontains",
1092-
"startswith",
1093-
"istartswith",
1094-
"endswith",
1095-
"iendswith",
1096-
"exact",
1097-
"iexact",
1098-
]
1096+
match_operators = [*STRING_OPERATORS]
10991097

11001098
if op in match_operators and isinstance(value, str):
11011099
return StringField().prepare_query_value(op, value)

mongoengine/queryset/transform.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
"iendswith",
5252
"exact",
5353
"iexact",
54+
"regex",
55+
"iregex",
56+
"wholeword",
57+
"iwholeword",
5458
)
5559
CUSTOM_OPERATORS = ("match",)
5660
MATCH_OPERATORS = (

tests/queryset/test_queryset.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,34 @@ def test_regex_query_shortcuts(self):
12551255
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
12561256
assert obj is None
12571257

1258+
# Test wholeword
1259+
obj = self.Person.objects(name__wholeword="Guido").first()
1260+
assert obj == person
1261+
obj = self.Person.objects(name__wholeword="rossum").first()
1262+
assert obj is None
1263+
obj = self.Person.objects(name__wholeword="Rossu").first()
1264+
assert obj is None
1265+
1266+
# Test iwholeword
1267+
obj = self.Person.objects(name__iwholeword="rOSSUM").first()
1268+
assert obj == person
1269+
obj = self.Person.objects(name__iwholeword="rOSSU").first()
1270+
assert obj is None
1271+
1272+
# Test regex
1273+
obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first()
1274+
assert obj == person
1275+
obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first()
1276+
assert obj is None
1277+
obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first()
1278+
assert obj is None
1279+
1280+
# Test iregex
1281+
obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first()
1282+
assert obj == person
1283+
obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first()
1284+
assert obj is None
1285+
12581286
# Test unsafe expressions
12591287
person = self.Person(name="Guido van Rossum [.'Geek']")
12601288
person.save()
@@ -1339,7 +1367,14 @@ def test_filter_chaining_with_regex(self):
13391367
person.save()
13401368

13411369
people = self.Person.objects
1342-
people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
1370+
people = (
1371+
people.filter(name__startswith="Gui")
1372+
.filter(name__not__endswith="tum")
1373+
.filter(name__icontains="VAN")
1374+
.filter(name__regex="^Guido")
1375+
.filter(name__wholeword="Guido")
1376+
.filter(name__wholeword="van")
1377+
)
13431378
assert people.count() == 1
13441379

13451380
def assertSequence(self, qs, expected):

0 commit comments

Comments
 (0)