summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHunternif <hunternif@gmail.com>2021-09-02 01:03:48 +0100
committerHunternif <hunternif@gmail.com>2021-09-02 02:01:49 +0100
commit89d2885327f4c2080edc1e4115dd592a7220d071 (patch)
tree20a822a7975766831c5f954c31f967dde2278dc7
parent125c43177506dae11677ac3a9f2f5703881d1d1e (diff)
server: implement similar posts as a single query
-rw-r--r--server/szurubooru/func/similar.py53
-rw-r--r--server/szurubooru/tests/func/test_similar.py36
2 files changed, 20 insertions, 69 deletions
diff --git a/server/szurubooru/func/similar.py b/server/szurubooru/func/similar.py
index 2e100ab..1a84e79 100644
--- a/server/szurubooru/func/similar.py
+++ b/server/szurubooru/func/similar.py
@@ -1,8 +1,8 @@
-from math import ceil
-from queue import Queue
from typing import List
-from szurubooru import model, search
+import sqlalchemy as sa
+
+from szurubooru import db, model, search
_search_executor_config = search.configs.PostSearchConfig()
_search_executor = search.Executor(_search_executor_config)
@@ -11,33 +11,20 @@ _search_executor = search.Executor(_search_executor_config)
def find_similar_posts(
source_post: model.Post, limit: int, query_text: str = ''
) -> List[model.Post]:
- results = []
- # Sort tags in order of increasing post count, i.e. least to most popular
- # This will help yield results quicker
- source_tags = sorted(source_post.tags, key=lambda t: t.post_count)
- source_tag_count = len(source_tags)
- max_removals = source_tag_count - 1
-
- tags = source_tags
- for x in range(max_removals + 1):
- # prepare the current search, remove known results
- query = query_text + ' ' + ' '.join([t.first_name for t in tags])
- query += ' -id:%d' % source_post.post_id
- for r in results:
- query += ' -id:%d' % r.post_id
-
- # execute
- _, posts = _search_executor.execute(query, 0, limit - len(results))
-
- # update results
- for p in posts:
- results.append(p)
- if len(results) >= limit:
- break
-
- # remove the least popular tag
- if len(tags) <= 1:
- break
- tags = tags[1:]
-
- return results
+ post_alias = sa.orm.aliased(model.Post)
+ pt_alias = sa.orm.aliased(model.PostTag)
+ result = (
+ db.session.query(post_alias)
+ .join(pt_alias, pt_alias.post_id == post_alias.post_id)
+ .filter(
+ sa.sql.or_(
+ pt_alias.tag_id == tag.tag_id for tag in source_post.tags
+ )
+ )
+ .filter(pt_alias.post_id != source_post.post_id)
+ .group_by(post_alias.post_id)
+ .order_by(sa.func.count(pt_alias.tag_id).desc())
+ .order_by(post_alias.post_id.desc())
+ .limit(limit)
+ )
+ return result
diff --git a/server/szurubooru/tests/func/test_similar.py b/server/szurubooru/tests/func/test_similar.py
index 486f67b..5346cb0 100644
--- a/server/szurubooru/tests/func/test_similar.py
+++ b/server/szurubooru/tests/func/test_similar.py
@@ -13,7 +13,6 @@ def verify_posts():
return verify
-# I'd like my behavior to be like this
def test_find_similar_posts(post_factory, tag_factory, verify_posts):
tagA = tag_factory(names=["a"])
tagB = tag_factory(names=["b"])
@@ -45,38 +44,6 @@ def test_find_similar_posts(post_factory, tag_factory, verify_posts):
verify_posts(results, [postABC, postBC, postAB, postA])
-# but it's actually like this for performance reasons
-def test_find_similar_posts_naive(post_factory, tag_factory, verify_posts):
- tagA = tag_factory(names=["a"]) # count=4
- tagB = tag_factory(names=["b"]) # count=3
- tagC = tag_factory(names=["c"]) # count=3
- postA = post_factory(id=1, tags=[tagA])
- postAB = post_factory(id=2, tags=[tagA, tagB])
- postAC = post_factory(id=3, tags=[tagA, tagC])
- postABC = post_factory(id=4, tags=[tagA, tagB, tagC])
- postBC = post_factory(id=5, tags=[tagB, tagC])
- db.session.add_all([tagA, tagB, tagC, postA, postAB, postAC, postABC, postBC])
- db.session.flush()
-
- results = similar.find_similar_posts(postBC, 10)
- verify_posts(results, [postABC, postAC])
-
- results = similar.find_similar_posts(postBC, 2)
- verify_posts(results, [postABC, postAC])
-
- results = similar.find_similar_posts(postABC, 10)
- verify_posts(results, [postAC, postAB, postA])
-
- results = similar.find_similar_posts(postA, 10)
- verify_posts(results, [postABC, postAC, postAB]) # sorted by id
-
- results = similar.find_similar_posts(postAB, 10)
- verify_posts(results, [postABC, postAC, postA])
-
- results = similar.find_similar_posts(postAC, 10)
- verify_posts(results, [postABC, postAB, postA])
-
-
def test_find_similar_posts_with_limit(post_factory, tag_factory, verify_posts):
tagA = tag_factory(names=["a"])
tagB = tag_factory(names=["b"])
@@ -90,7 +57,4 @@ def test_find_similar_posts_with_limit(post_factory, tag_factory, verify_posts):
db.session.flush()
results = similar.find_similar_posts(postABCDE, 10)
- # I'd like it to be like this:
- # verify_posts(results, [postAB])
- # but it's like this for performance reasons:
verify_posts(results, [postAB, postA])