Handle TAG queries that include the separator
This commit is contained in:
parent
b46408ccd2
commit
8f32b359f0
7 changed files with 591 additions and 128 deletions
|
@ -163,13 +163,13 @@ def test_updates_a_model(members):
|
|||
# Or, affecting multiple model instances with an implicit save:
|
||||
Member.find(Member.last_name == "Brookins").update(last_name="Smith")
|
||||
results = Member.find(Member.last_name == "Smith")
|
||||
assert sorted(results) == members
|
||||
assert results == members
|
||||
|
||||
|
||||
def test_paginate_query(members):
|
||||
member1, member2, member3 = members
|
||||
actual = Member.find().all(batch_size=1)
|
||||
assert sorted(actual) == [member1, member2, member3]
|
||||
assert actual == [member1, member2, member3]
|
||||
|
||||
|
||||
def test_access_result_by_index_cached(members):
|
||||
|
@ -202,7 +202,7 @@ def test_exact_match_queries(members):
|
|||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(Member.last_name == "Brookins").all()
|
||||
assert sorted(actual) == [member1, member2]
|
||||
assert actual == [member1, member2]
|
||||
|
||||
actual = Member.find(
|
||||
(Member.last_name == "Brookins") & ~(Member.first_name == "Andrew")).all()
|
||||
|
@ -218,7 +218,7 @@ def test_exact_match_queries(members):
|
|||
(Member.last_name == "Brookins") & (Member.first_name == "Andrew")
|
||||
| (Member.first_name == "Kim")
|
||||
).all()
|
||||
assert actual == [member2, member1]
|
||||
assert actual == [member1, member2]
|
||||
|
||||
actual = Member.find(Member.first_name == "Kim", Member.last_name == "Brookins").all()
|
||||
assert actual == [member2]
|
||||
|
@ -230,7 +230,7 @@ def test_recursive_query_resolution(members):
|
|||
actual = Member.find((Member.last_name == "Brookins") | (
|
||||
Member.age == 100
|
||||
) & (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member1, member2, member3]
|
||||
assert actual == [member1, member2, member3]
|
||||
|
||||
|
||||
def test_tag_queries_boolean_logic(members):
|
||||
|
@ -239,35 +239,107 @@ def test_tag_queries_boolean_logic(members):
|
|||
actual = Member.find(
|
||||
(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
|
||||
def test_tag_queries_punctuation():
|
||||
member = Member(
|
||||
first_name="Andrew the Michael",
|
||||
member1 = Member(
|
||||
first_name="Andrew, the Michael",
|
||||
last_name="St. Brookins-on-Pier",
|
||||
email="a@example.com",
|
||||
email="a|b@example.com", # NOTE: This string uses the TAG field separator.
|
||||
age=38,
|
||||
join_date=today
|
||||
join_date=today,
|
||||
)
|
||||
member.save()
|
||||
member1.save()
|
||||
|
||||
assert Member.find(Member.first_name == "Andrew the Michael").first() == member
|
||||
assert Member.find(Member.last_name == "St. Brookins-on-Pier").first() == member
|
||||
assert Member.find(Member.email == "a@example.com").first() == member
|
||||
member2 = Member(
|
||||
first_name="Bob",
|
||||
last_name="the Villain",
|
||||
email="a|villain@example.com", # NOTE: This string uses the TAG field separator.
|
||||
age=38,
|
||||
join_date=today,
|
||||
)
|
||||
member2.save()
|
||||
|
||||
assert Member.find(Member.first_name == "Andrew, the Michael").first() == member1
|
||||
assert Member.find(Member.last_name == "St. Brookins-on-Pier").first() == member1
|
||||
|
||||
# Notice that when we index and query multiple values that use the internal
|
||||
# TAG separator for single-value exact-match fields, like an indexed string,
|
||||
# the queries will succeed. We apply a workaround that queries for the union
|
||||
# of the two values separated by the tag separator.
|
||||
assert Member.find(Member.email == "a|b@example.com").all() == [member1]
|
||||
assert Member.find(Member.email == "a|villain@example.com").all() == [member2]
|
||||
|
||||
|
||||
def test_tag_queries_negation(members):
|
||||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(
|
||||
"""
|
||||
┌first_name
|
||||
NOT EQ┤
|
||||
└Andrew
|
||||
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew")
|
||||
)
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
AND┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Brookins
|
||||
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") & (Member.last_name == "Brookins")
|
||||
)
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
AND┤
|
||||
| ┌last_name
|
||||
| ┌EQ┤
|
||||
| | └Brookins
|
||||
└OR┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Smith
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member2, member3]
|
||||
((Member.last_name == "Brookins") | (Member.last_name == "Smith")))
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
┌AND┤
|
||||
| | ┌last_name
|
||||
| └EQ┤
|
||||
| └Brookins
|
||||
OR┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Smith
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith"))
|
||||
assert query.all() == [member2, member3]
|
||||
|
||||
actual = Member.find(
|
||||
(Member.first_name == "Andrew") & ~(Member.last_name == "Brookins")).all()
|
||||
assert sorted(actual) == [member3]
|
||||
assert actual == [member3]
|
||||
|
||||
|
||||
def test_numeric_queries(members):
|
||||
|
@ -277,7 +349,7 @@ def test_numeric_queries(members):
|
|||
assert actual == [member2]
|
||||
|
||||
actual = Member.find(Member.age > 34).all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
actual = Member.find(Member.age < 35).all()
|
||||
assert actual == [member2]
|
||||
|
@ -289,17 +361,17 @@ def test_numeric_queries(members):
|
|||
assert actual == [member3]
|
||||
|
||||
actual = Member.find(~(Member.age == 100)).all()
|
||||
assert sorted(actual) == [member1, member2]
|
||||
assert actual == [member1, member2]
|
||||
|
||||
|
||||
def test_sorting(members):
|
||||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(Member.age > 34).sort_by('age').all()
|
||||
assert sorted(actual) == [member3, member1]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
actual = Member.find(Member.age > 34).sort_by('-age').all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member3, member1]
|
||||
|
||||
with pytest.raises(QueryNotSupportedError):
|
||||
# This field does not exist.
|
||||
|
|
|
@ -44,8 +44,8 @@ class Order(BaseJsonModel):
|
|||
|
||||
|
||||
class Member(BaseJsonModel):
|
||||
first_name: str
|
||||
last_name: str
|
||||
first_name: str = Field(index=True)
|
||||
last_name: str = Field(index=True)
|
||||
email: str = Field(index=True)
|
||||
join_date: datetime.date
|
||||
age: int = Field(index=True)
|
||||
|
@ -190,7 +190,7 @@ def test_updates_a_model(members):
|
|||
# Or, affecting multiple model instances with an implicit save:
|
||||
Member.find(Member.last_name == "Brookins").update(last_name="Smith")
|
||||
results = Member.find(Member.last_name == "Smith")
|
||||
assert sorted(results) == members
|
||||
assert results == members
|
||||
|
||||
# Or, updating a field in an embedded model:
|
||||
member2.update(address__city="Happy Valley")
|
||||
|
@ -200,7 +200,7 @@ def test_updates_a_model(members):
|
|||
def test_paginate_query(members):
|
||||
member1, member2, member3 = members
|
||||
actual = Member.find().all(batch_size=1)
|
||||
assert sorted(actual) == [member1, member2, member3]
|
||||
assert actual == [member1, member2, member3]
|
||||
|
||||
|
||||
def test_access_result_by_index_cached(members):
|
||||
|
@ -233,7 +233,7 @@ def test_exact_match_queries(members):
|
|||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(Member.last_name == "Brookins").all()
|
||||
assert sorted(actual) == [member1, member2]
|
||||
assert actual == [member1, member2]
|
||||
|
||||
actual = Member.find(
|
||||
(Member.last_name == "Brookins") & ~(Member.first_name == "Andrew")).all()
|
||||
|
@ -264,7 +264,7 @@ def test_recursive_query_resolution(members):
|
|||
actual = Member.find((Member.last_name == "Brookins") | (
|
||||
Member.age == 100
|
||||
) & (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member1, member2, member3]
|
||||
assert actual == [member1, member2, member3]
|
||||
|
||||
|
||||
def test_tag_queries_boolean_logic(members):
|
||||
|
@ -273,35 +273,109 @@ def test_tag_queries_boolean_logic(members):
|
|||
actual = Member.find(
|
||||
(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
|
||||
def test_tag_queries_punctuation():
|
||||
member = Member(
|
||||
first_name="Andrew, the Michael", # This string uses the TAG field separator.
|
||||
def test_tag_queries_punctuation(address):
|
||||
member1 = Member(
|
||||
first_name="Andrew, the Michael",
|
||||
last_name="St. Brookins-on-Pier",
|
||||
email="a@example.com",
|
||||
email="a|b@example.com", # NOTE: This string uses the TAG field separator.
|
||||
age=38,
|
||||
join_date=today
|
||||
join_date=today,
|
||||
address=address
|
||||
)
|
||||
member.save()
|
||||
member1.save()
|
||||
|
||||
assert Member.find(Member.first_name == "Andrew the Michael").first() == member
|
||||
assert Member.find(Member.last_name == "St. Brookins-on-Pier").first() == member
|
||||
assert Member.find(Member.email == "a@example.com").first() == member
|
||||
member2 = Member(
|
||||
first_name="Bob",
|
||||
last_name="the Villain",
|
||||
email="a|villain@example.com", # NOTE: This string uses the TAG field separator.
|
||||
age=38,
|
||||
join_date=today,
|
||||
address=address
|
||||
)
|
||||
member2.save()
|
||||
|
||||
assert Member.find(Member.first_name == "Andrew, the Michael").first() == member1
|
||||
assert Member.find(Member.last_name == "St. Brookins-on-Pier").first() == member1
|
||||
|
||||
# Notice that when we index and query multiple values that use the internal
|
||||
# TAG separator for single-value exact-match fields, like an indexed string,
|
||||
# the queries will succeed. We apply a workaround that queries for the union
|
||||
# of the two values separated by the tag separator.
|
||||
assert Member.find(Member.email == "a|b@example.com").all() == [member1]
|
||||
assert Member.find(Member.email == "a|villain@example.com").all() == [member2]
|
||||
|
||||
|
||||
def test_tag_queries_negation(members):
|
||||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(
|
||||
"""
|
||||
┌first_name
|
||||
NOT EQ┤
|
||||
└Andrew
|
||||
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew")
|
||||
)
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
AND┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Brookins
|
||||
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") & (Member.last_name == "Brookins")
|
||||
)
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
AND┤
|
||||
| ┌last_name
|
||||
| ┌EQ┤
|
||||
| | └Brookins
|
||||
└OR┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Smith
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith")).all()
|
||||
assert sorted(actual) == [member2, member3]
|
||||
((Member.last_name == "Brookins") | (Member.last_name == "Smith")))
|
||||
assert query.all() == [member2]
|
||||
|
||||
"""
|
||||
┌first_name
|
||||
┌NOT EQ┤
|
||||
| └Andrew
|
||||
┌AND┤
|
||||
| | ┌last_name
|
||||
| └EQ┤
|
||||
| └Brookins
|
||||
OR┤
|
||||
| ┌last_name
|
||||
└EQ┤
|
||||
└Smith
|
||||
"""
|
||||
query = Member.find(
|
||||
~(Member.first_name == "Andrew") &
|
||||
(Member.last_name == "Brookins") | (Member.last_name == "Smith"))
|
||||
assert query.all() == [member2, member3]
|
||||
|
||||
actual = Member.find(
|
||||
(Member.first_name == "Andrew") & ~(Member.last_name == "Brookins")).all()
|
||||
assert sorted(actual) == [member3]
|
||||
assert actual == [member3]
|
||||
|
||||
|
||||
def test_numeric_queries(members):
|
||||
|
@ -311,7 +385,7 @@ def test_numeric_queries(members):
|
|||
assert actual == [member2]
|
||||
|
||||
actual = Member.find(Member.age > 34).all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
actual = Member.find(Member.age < 35).all()
|
||||
assert actual == [member2]
|
||||
|
@ -323,17 +397,17 @@ def test_numeric_queries(members):
|
|||
assert actual == [member3]
|
||||
|
||||
actual = Member.find(~(Member.age == 100)).all()
|
||||
assert sorted(actual) == [member1, member2]
|
||||
assert actual == [member1, member2]
|
||||
|
||||
|
||||
def test_sorting(members):
|
||||
member1, member2, member3 = members
|
||||
|
||||
actual = Member.find(Member.age > 34).sort_by('age').all()
|
||||
assert sorted(actual) == [member3, member1]
|
||||
assert actual == [member1, member3]
|
||||
|
||||
actual = Member.find(Member.age > 34).sort_by('-age').all()
|
||||
assert sorted(actual) == [member1, member3]
|
||||
assert actual == [member3, member1]
|
||||
|
||||
with pytest.raises(QueryNotSupportedError):
|
||||
# This field does not exist.
|
||||
|
@ -354,7 +428,10 @@ def test_schema():
|
|||
assert Member.redisearch_schema() == "ON JSON PREFIX 1 " \
|
||||
"redis-developer:tests.test_json_model.Member: " \
|
||||
"SCHEMA $.pk AS pk TAG " \
|
||||
"$.first_name AS first_name TAG " \
|
||||
"$.last_name AS last_name TAG " \
|
||||
"$.email AS email TAG " \
|
||||
"$.age AS age NUMERIC " \
|
||||
"$.address.pk AS address_pk TAG " \
|
||||
"$.address.postal_code AS address_postal_code TAG " \
|
||||
"$.orders[].pk AS orders_pk TAG " \
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue