Skip to content

Commit

Permalink
Initial "filter entries by entry tags" implementation. #328
Browse files Browse the repository at this point in the history
  • Loading branch information
lemon24 committed Nov 25, 2023
1 parent 6fd9325 commit 37e62b2
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 27 deletions.
93 changes: 71 additions & 22 deletions src/reader/_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ def delete_tag(self, resource_id: ResourceId, key: str) -> None:
WHERE (
{', '.join(columns)}
) = (
{', '.join(('?' for _ in columns))}
{', '.join('?' for _ in columns)}
)
"""
params = resource_id + (key,)
Expand Down Expand Up @@ -1512,7 +1512,7 @@ def apply_entry_filter(
query: Query, filter: EntryFilter, keyword: str = 'WHERE'
) -> dict[str, Any]:
add = getattr(query, keyword)
feed_url, entry_id, read, important, has_enclosures, feed_tags = filter
feed_url, entry_id, read, important, has_enclosures, tags, feed_tags = filter

context = {}

Expand All @@ -1538,6 +1538,7 @@ def apply_entry_filter(
"""
)

context.update(apply_entry_tags_filter(query, tags, keyword=keyword))
context.update(
apply_feed_tags_filter(query, feed_tags, 'entries.feed', keyword=keyword)
)
Expand All @@ -1546,15 +1547,68 @@ def apply_entry_filter(


def apply_feed_tags_filter(
query: Query,
tags: TagFilter,
url_column: str,
keyword: str = 'WHERE',
) -> dict[str, Any]:
query: Query, tags: TagFilter, url_column: str, keyword: str = 'WHERE'
) -> dict[str, str]:
context, tags_cte, tags_count_cte = apply_tags_filter(
query, tags, keyword, 'feed_tags'
)

if tags_cte:
query.WITH((tags_cte, f"SELECT key FROM feed_tags WHERE feed = {url_column}"))

if tags_count_cte:
query.WITH(
(
tags_count_cte,
f"SELECT count(key) FROM feed_tags WHERE feed = {url_column}",
)
)

return context


def apply_entry_tags_filter(
query: Query, tags: TagFilter, keyword: str = 'WHERE'
) -> dict[str, str]:
context, tags_cte, tags_count_cte = apply_tags_filter(
query, tags, keyword, 'entry_tags'
)

if tags_cte:
query.WITH(
(
tags_cte,
"""
SELECT key FROM entry_tags
WHERE (id, feed) = (entries.id, entries.feed)
""",
)
)

if tags_count_cte:
query.WITH(
(
tags_count_cte,
"""
SELECT count(key) FROM entry_tags
WHERE (id, feed) = (entries.id, entries.feed)
""",
)
)

return context


def apply_tags_filter(
query: Query, tags: TagFilter, keyword: str, base_table: str
) -> tuple[dict[str, str], str | None, str | None]:
add = getattr(query, keyword)

context = {}

tags_cte = f'__{base_table}'
tags_count_cte = f'__{base_table}_count'

add_tags_cte = False
add_tags_count_cte = False

Expand All @@ -1566,31 +1620,26 @@ def apply_feed_tags_filter(

for maybe_tag in subtags:
if isinstance(maybe_tag, bool):
tag_add(f"{'NOT' if not maybe_tag else ''} (SELECT * FROM tags_count)")
tag_add(
f"{'NOT' if not maybe_tag else ''} (SELECT * FROM {tags_count_cte})"
)
add_tags_count_cte = True
continue

is_negation, tag = maybe_tag
tag_name = f'__tag_{next_tag_id}'
tag_name = f'__{base_table}_{next_tag_id}'
next_tag_id += 1
context[tag_name] = tag
tag_add(f":{tag_name} {'NOT' if is_negation else ''} IN tags")
tag_add(f":{tag_name} {'NOT' if is_negation else ''} IN {tags_cte}")
add_tags_cte = True

add(str(tag_query))

if add_tags_cte:
query.WITH(("tags", f"SELECT key FROM feed_tags WHERE feed = {url_column}"))

if add_tags_count_cte:
query.WITH(
(
"tags_count",
f"SELECT count(key) FROM feed_tags WHERE feed = {url_column}",
)
)

return context
return (
context,
tags_cte if add_tags_cte else None,
tags_count_cte if add_tags_count_cte else None,
)


def make_recent_last_select(id_prefix: str = 'entries.') -> Sequence[Any]:
Expand Down
11 changes: 10 additions & 1 deletion src/reader/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ class EntryFilter(NamedTuple):
read: bool | None = None
important: TristateFilter = 'any'
has_enclosures: bool | None = None
tags: TagFilter = ()
feed_tags: TagFilter = ()

@classmethod
Expand All @@ -500,6 +501,7 @@ def from_args(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> Self:
feed_url = _feed_argument(feed) if feed is not None else None
Expand All @@ -517,10 +519,17 @@ def from_args(
if has_enclosures not in (None, False, True):
raise ValueError("has_enclosures should be one of (None, False, True)")

tag_filter = tag_filter_argument(tags)
feed_tag_filter = tag_filter_argument(feed_tags, 'feed_tags')

return cls(
feed_url, entry_id, read, important_filter, has_enclosures, feed_tag_filter
feed_url,
entry_id,
read,
important_filter,
has_enclosures,
tag_filter,
feed_tag_filter,
)


Expand Down
12 changes: 8 additions & 4 deletions src/reader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,7 @@ def get_entries(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
sort: EntrySort = 'recent',
limit: int | None = None,
Expand Down Expand Up @@ -1177,7 +1178,7 @@ def get_entries(
# https://specs.openstack.org/openstack/api-wg/guidelines/pagination_filter_sort.html

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, feed_tags
feed, entry, read, important, has_enclosures, tags, feed_tags
)

if sort not in ('recent', 'random'):
Expand Down Expand Up @@ -1246,6 +1247,7 @@ def get_entry_counts(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> EntryCounts:
"""Count all or some of the entries.
Expand Down Expand Up @@ -1280,7 +1282,7 @@ def get_entry_counts(
"""

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, feed_tags
feed, entry, read, important, has_enclosures, tags, feed_tags
)
now = self._now()
return self._storage.get_entry_counts(now, filter)
Expand Down Expand Up @@ -1641,6 +1643,7 @@ def search_entries(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
sort: SearchSortOrder = 'relevant',
limit: int | None = None,
Expand Down Expand Up @@ -1750,7 +1753,7 @@ def search_entries(
"""
filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, feed_tags
feed, entry, read, important, has_enclosures, tags, feed_tags
)

if sort not in ('relevant', 'recent', 'random'):
Expand All @@ -1776,6 +1779,7 @@ def search_entry_counts(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> EntrySearchCounts:
"""Count entries matching a full-text search query.
Expand Down Expand Up @@ -1820,7 +1824,7 @@ def search_entry_counts(
"""

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, feed_tags
feed, entry, read, important, has_enclosures, tags, feed_tags
)
now = self._now()
return self._search.search_entry_counts(query, now, filter)
Expand Down

0 comments on commit 37e62b2

Please sign in to comment.