Skip to content

Commit

Permalink
Merge pull request #2568 from idoshr/regex_query
Browse files Browse the repository at this point in the history
Regex and whole word text search query
  • Loading branch information
bagerard committed Oct 12, 2021
2 parents 0af1a11 + 7a0a58c commit 8c3e2b3
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 13 deletions.
4 changes: 4 additions & 0 deletions docs/guide/querying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ expressions:
* ``istartswith`` -- string field starts with value (case insensitive)
* ``endswith`` -- string field ends with value
* ``iendswith`` -- string field ends with value (case insensitive)
* ``wholeword`` -- string field contains whole word
* ``iwholeword`` -- string field contains whole word (case insensitive)
* ``regex`` -- string field match by regex
* ``iregex`` -- string field match by regex (case insensitive)
* ``match`` -- performs an $elemMatch so you can match an entire document within an array


Expand Down
22 changes: 10 additions & 12 deletions mongoengine/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,17 @@ def prepare_query_value(self, op, value):
regex = r"%s$"
elif op == "exact":
regex = r"^%s$"
elif op == "wholeword":
regex = r"\b%s\b"
elif op == "regex":
regex = value

# escape unsafe characters which could lead to a re.error
value = re.escape(value)
value = re.compile(regex % value, flags)
if op == "regex":
value = re.compile(regex, flags)
else:
value = re.escape(value)
value = re.compile(regex % value, flags)
return super().prepare_query_value(op, value)


Expand Down Expand Up @@ -1086,16 +1093,7 @@ def lookup_member(self, member_name):
return DictField(db_field=member_name)

def prepare_query_value(self, op, value):
match_operators = [
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"exact",
"iexact",
]
match_operators = [*STRING_OPERATORS]

if op in match_operators and isinstance(value, str):
return StringField().prepare_query_value(op, value)
Expand Down
4 changes: 4 additions & 0 deletions mongoengine/queryset/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"iendswith",
"exact",
"iexact",
"regex",
"iregex",
"wholeword",
"iwholeword",
)
CUSTOM_OPERATORS = ("match",)
MATCH_OPERATORS = (
Expand Down
37 changes: 36 additions & 1 deletion tests/queryset/test_queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,34 @@ def test_regex_query_shortcuts(self):
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
assert obj is None

# Test wholeword
obj = self.Person.objects(name__wholeword="Guido").first()
assert obj == person
obj = self.Person.objects(name__wholeword="rossum").first()
assert obj is None
obj = self.Person.objects(name__wholeword="Rossu").first()
assert obj is None

# Test iwholeword
obj = self.Person.objects(name__iwholeword="rOSSUM").first()
assert obj == person
obj = self.Person.objects(name__iwholeword="rOSSU").first()
assert obj is None

# Test regex
obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first()
assert obj == person
obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first()
assert obj is None
obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first()
assert obj is None

# Test iregex
obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first()
assert obj == person
obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first()
assert obj is None

# Test unsafe expressions
person = self.Person(name="Guido van Rossum [.'Geek']")
person.save()
Expand Down Expand Up @@ -1339,7 +1367,14 @@ def test_filter_chaining_with_regex(self):
person.save()

people = self.Person.objects
people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
people = (
people.filter(name__startswith="Gui")
.filter(name__not__endswith="tum")
.filter(name__icontains="VAN")
.filter(name__regex="^Guido")
.filter(name__wholeword="Guido")
.filter(name__wholeword="van")
)
assert people.count() == 1

def assertSequence(self, qs, expected):
Expand Down

0 comments on commit 8c3e2b3

Please sign in to comment.