Support GTE and LTE on color and rarity

This commit is contained in:
Correl Roush 2021-07-15 23:56:25 -04:00
parent dc3cc6427b
commit a1cf52312e
2 changed files with 92 additions and 48 deletions

View file

@ -102,38 +102,56 @@ async def advanced_search(
params = {} params = {}
sets = [] sets = []
logging.debug("Performing search for: %s", search) logger.debug("Performing search for: %s", search)
for i, criterion in enumerate(search.criteria): for i, criterion in enumerate(search.criteria):
param = f"param_{i}" param = f"param_{i}"
if isinstance(criterion, tutor.search.Name): if isinstance(criterion, tutor.search.Name):
constraints.append(f"cards.name LIKE :{param}") constraints.append(f"cards.name LIKE :{param}")
params[param] = f"%{criterion.name}%" params[param] = f"%{criterion.text}%"
if isinstance(criterion, tutor.search.Type): if isinstance(criterion, tutor.search.Type):
constraints.append(f"cards.type_line LIKE :{param}") constraints.append(f"cards.type_line LIKE :{param}")
params[param] = f"%{criterion.name}%" params[param] = f"%{criterion.text}%"
if isinstance(criterion, tutor.search.IsExpansion): if isinstance(criterion, tutor.search.Expansion):
constraints.append(f"cards.set_code LIKE :{param}") constraints.append(f"cards.set_code LIKE :{param}")
params[param] = criterion.set_code params[param] = criterion.set_code
if isinstance(criterion, tutor.search.InExpansion): if isinstance(criterion, tutor.search.Color):
sets.append(criterion.set_code) if criterion.operator == tutor.search.Operator.matches:
if isinstance(criterion, tutor.search.IsColor): constraints.append(f"cards.color_identity LIKE :{param}")
constraints.append(f"cards.color_identity LIKE :{param}") params[param] = tutor.models.Color.to_string(criterion.colors)
params[param] = tutor.models.Color.to_string(criterion.colors) if criterion.operator == tutor.search.Operator.lte:
if isinstance(criterion, tutor.search.IsRarity): constraints.append(
constraints.append(f"cards.rarity LIKE :{param}") "({})".format(
params[param] = str(criterion.rarity) " OR ".join(
[
f"cards.color_identity LIKE :{param}_{color}"
for color in criterion.colors
]
)
)
)
params.update(
{f"{param}_{color}": str(color) for color in criterion.colors}
)
params[param] = tutor.models.Color.to_string(criterion.colors)
if criterion.operator == tutor.search.Operator.gte:
constraints.append(f"cards.color_identity LIKE :{param}")
params[param] = "%{}%".format(
"%".join(tutor.models.Color.to_string(criterion.colors))
)
if isinstance(criterion, tutor.search.Rarity):
if criterion.operator == tutor.search.Operator.matches:
constraints.append(f"cards.rarity LIKE :{param}")
params[param] = str(criterion.rarity)
if criterion.operator == tutor.search.Operator.lte:
constraints.append(f"rarities.rarity_ord <= :{param}")
params[param] = criterion.rarity.value
if criterion.operator == tutor.search.Operator.gte:
constraints.append(f"rarities.rarity_ord >= :{param}")
params[param] = criterion.rarity.value
if isinstance(criterion, tutor.search.Oracle): if isinstance(criterion, tutor.search.Oracle):
constraints.append(f"cards.oracle_text LIKE :{param}") constraints.append(f"cards.oracle_text LIKE :{param}")
params[param] = f"%{criterion.text}%" params[param] = f"%{criterion.text}%"
if sets:
set_params = {f"set_{i}": set_code for i, set_code in enumerate(sets)}
constraints.append(
"cards.set_code IN ({})".format(
", ".join([f":{key}" for key in set_params.keys()])
)
)
params.update(set_params)
if in_collection is not None: if in_collection is not None:
if in_collection: if in_collection:
joins.append("JOIN copies ON (cards.scryfall_id = copies.scryfall_id)") joins.append("JOIN copies ON (cards.scryfall_id = copies.scryfall_id)")
@ -157,6 +175,7 @@ async def advanced_search(
f"LIMIT {offset},{limit}", f"LIMIT {offset},{limit}",
] ]
) )
logger.debug("Query: %s", (query, params))
cursor = await db.execute(query, params) cursor = await db.execute(query, params)
rows = await cursor.fetchall() rows = await cursor.fetchall()
return [ return [

View file

@ -1,4 +1,5 @@
import dataclasses import dataclasses
import enum
import functools import functools
import typing import typing
@ -7,38 +8,35 @@ import parsy
import tutor.models import tutor.models
class Operator(enum.Enum):
matches = ":"
gte = ">="
lte = "<="
@dataclasses.dataclass
class Criterion: class Criterion:
... operator: Operator
@dataclasses.dataclass @dataclasses.dataclass
class IsExpansion(Criterion): class Expansion(Criterion):
set_code: parsy.string set_code: parsy.string
@dataclasses.dataclass @dataclasses.dataclass
class InExpansion(Criterion): class Color(Criterion):
set_code: parsy.string
@dataclasses.dataclass
class IsColor(Criterion):
colors: typing.Set[tutor.models.Color] colors: typing.Set[tutor.models.Color]
@dataclasses.dataclass @dataclasses.dataclass
class IsRarity(Criterion): class Rarity(Criterion):
rarity: tutor.models.Rarity rarity: tutor.models.Rarity
@dataclasses.dataclass
class Name(Criterion):
name: parsy.string
@dataclasses.dataclass @dataclasses.dataclass
class Type(Criterion): class Type(Criterion):
name: parsy.string text: parsy.string
@dataclasses.dataclass @dataclasses.dataclass
@ -46,6 +44,12 @@ class Oracle(Criterion):
text: parsy.string text: parsy.string
@dataclasses.dataclass
class Name(Criterion):
operator: Operator = Operator.matches
text: parsy.string = ""
@dataclasses.dataclass @dataclasses.dataclass
class Search: class Search:
criteria: typing.List[Criterion] criteria: typing.List[Criterion]
@ -63,7 +67,10 @@ W, U, B, G, R = (
tutor.models.Color.Green, tutor.models.Color.Green,
tutor.models.Color.Red, tutor.models.Color.Red,
) )
matches = parsy.string(":")
matches = parsy.string(":").map(Operator)
gte = parsy.string(">=").map(Operator)
lte = parsy.string("<=").map(Operator)
color = ( color = (
ustring("w").result(W) ustring("w").result(W)
@ -112,38 +119,56 @@ wedge = (
| lstring("temur").result({U, R, G}) | lstring("temur").result({U, R, G})
) )
colors = single_color | guild | shard | wedge | multicolor any_color = single_color | guild | shard | wedge | multicolor
colors = parsy.seq(
_keyword=lstring_from("c", "color", "colors"),
operator=matches | gte | lte,
colors=any_color,
).combine_dict(Color)
expansion_string = ( expansion_string = (
parsy.regex(r"[a-zA-Z0-9]+").map(lambda s: s.upper()).desc("expansion set code") parsy.regex(r"[a-zA-Z0-9]+").map(lambda s: s.upper()).desc("expansion set code")
) )
is_expansion = lstring_from("e", "expansion", "s", "set") >> matches >> expansion_string.map(IsExpansion) expansion = parsy.seq(
_keyword=lstring_from("e", "expansion", "s", "set"),
operator=matches,
set_code=expansion_string,
).combine_dict(Expansion)
in_expansion = lstring("in") >> matches >> expansion_string.map(InExpansion) # in_expansion = lstring("in") >> matches >> expansion_string.map(InExpansion)
rarity = ( any_rarity = (
lstring_from("c", "common").result(tutor.models.Rarity.Common) lstring_from("c", "common").result(tutor.models.Rarity.Common)
| lstring_from("u", "uncommon").result(tutor.models.Rarity.Uncommon) | lstring_from("u", "uncommon").result(tutor.models.Rarity.Uncommon)
| lstring_from("r", "rare").result(tutor.models.Rarity.Rare) | lstring_from("r", "rare").result(tutor.models.Rarity.Rare)
| lstring_from("m", "mythic").result(tutor.models.Rarity.Mythic) | lstring_from("m", "mythic").result(tutor.models.Rarity.Mythic)
) )
is_rarity = lstring_from("r", "rarity") >> matches >> rarity.map(IsRarity) rarity = parsy.seq(
_keyword=lstring_from("r", "rarity"),
is_color = lstring_from("c", "color", "colors") >> matches >> colors.map(IsColor) operator=matches | gte | lte,
rarity=any_rarity,
).combine_dict(Rarity)
string_literal = parsy.regex(r'"[^"]*"').map(lambda s: s[1:-1]) | parsy.regex(r"[^\s]+") string_literal = parsy.regex(r'"[^"]*"').map(lambda s: s[1:-1]) | parsy.regex(r"[^\s]+")
has_type = lstring_from("t", "type") >> matches >> string_literal.map(Type) type_line = parsy.seq(
_keyword=lstring_from("t", "type"),
operator=matches,
text=string_literal,
).combine_dict(Type)
has_oracle = lstring_from("o", "oracle") >> matches >> string_literal.map(Oracle) oracle = parsy.seq(
_keyword=lstring_from("o", "oracle"),
operator=matches,
text=string_literal,
).combine_dict(Oracle)
name = string_literal.map(Name) name = string_literal.map(Name)
criterion = ( criterion = colors | expansion | rarity | type_line | oracle | name
is_expansion | in_expansion | is_rarity | is_color | has_type | has_oracle | name
)
padding = parsy.regex(r"\s*") padding = parsy.regex(r"\s*")
search = padding >> (criterion << padding).many().map(Search) search = padding >> (criterion << padding).many().map(Search)