Handle TAG queries that include the separator

This commit is contained in:
Andrew Brookins 2021-10-05 16:40:02 -07:00
parent b46408ccd2
commit 8f32b359f0
7 changed files with 591 additions and 128 deletions

View file

@ -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.

View file

@ -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 " \