From 49780df80e0b9d0f4711c72c6b04e53dab6e3d1a Mon Sep 17 00:00:00 2001
From: ABW <andrzejl@syncad.com>
Date: Wed, 2 Dec 2020 20:08:02 +0100
Subject: [PATCH] [ABW]: [Fix] query updating hive_posts.active no longer stops
 update on older active (which can come from edit) - fixes second set of
 problems with active value in 1000_pairs test [Fix] delete_hive_post now
 properly updates active value - fixes first set of problems with it in
 1000_pairs test [Fix] order of posts in database_api.find_comments is now
 defined by order requested in call parameters (1000_pairs test) [Fix] added
 escaping to find_comments values (escape_chars test) [Fix] unified checks in
 various community related calls (in some cases it means there is check where
 there was none) added find_community_id SQL function that follows other find_
 routines (there is more places where it should be used, community related
 get_ranked_posts versions in particular)

---
 hive/db/sql_scripts/hive_post_operations.sql |  6 +-
 hive/db/sql_scripts/utility_functions.sql    | 24 +++++++
 hive/indexer/blocks.py                       |  2 +-
 hive/indexer/posts.py                        | 10 +--
 hive/server/bridge_api/methods.py            |  5 +-
 hive/server/database_api/methods.py          |  6 +-
 hive/server/hive_api/common.py               |  8 +--
 hive/server/hive_api/community.py            | 12 ++--
 hive/server/hive_api/objects.py              |  1 +
 hive/server/hive_api/public.py               |  1 +
 hive/utils/post_active.py                    | 69 ++++++++++----------
 tests/tests_api                              |  2 +-
 12 files changed, 88 insertions(+), 58 deletions(-)

diff --git a/hive/db/sql_scripts/hive_post_operations.sql b/hive/db/sql_scripts/hive_post_operations.sql
index 63a3d9d5b..3cdcadaeb 100644
--- a/hive/db/sql_scripts/hive_post_operations.sql
+++ b/hive/db/sql_scripts/hive_post_operations.sql
@@ -144,12 +144,13 @@ END
 $function$
 ;
 
-DROP FUNCTION if exists delete_hive_post(character varying,character varying,character varying, integer)
+DROP FUNCTION if exists delete_hive_post(character varying,character varying,character varying, integer, timestamp)
 ;
 CREATE OR REPLACE FUNCTION delete_hive_post(
   in _author hive_accounts.name%TYPE,
   in _permlink hive_permlink_data.permlink%TYPE,
-  in _block_num hive_blocks.num%TYPE)
+  in _block_num hive_blocks.num%TYPE,
+  in _date hive_posts.active%TYPE)
 RETURNS TABLE (id hive_posts.id%TYPE, depth hive_posts.depth%TYPE)
 LANGUAGE plpgsql
 AS
@@ -165,6 +166,7 @@ BEGIN
       WHERE ha.name = _author AND hpd.permlink = _permlink
     )
     , block_num = _block_num
+    , active = _date
   FROM hive_posts hp1
   INNER JOIN hive_accounts ha ON hp1.author_id = ha.id
   INNER JOIN hive_permlink_data hpd ON hp1.permlink_id = hpd.id
diff --git a/hive/db/sql_scripts/utility_functions.sql b/hive/db/sql_scripts/utility_functions.sql
index 896de4fe5..ae326d9a0 100644
--- a/hive/db/sql_scripts/utility_functions.sql
+++ b/hive/db/sql_scripts/utility_functions.sql
@@ -124,3 +124,27 @@ BEGIN
 END
 $function$
 ;
+
+DROP FUNCTION IF EXISTS public.find_community_id CASCADE
+;
+CREATE OR REPLACE FUNCTION public.find_community_id(
+    in _community_name hive_communities.name%TYPE,
+    in _check BOOLEAN
+)
+RETURNS INTEGER
+LANGUAGE 'plpgsql' STABLE
+AS
+$function$
+DECLARE
+  __community_id INT = 0;
+BEGIN
+  IF (_community_name <> '') THEN
+    SELECT INTO __community_id COALESCE( ( SELECT id FROM hive_communities WHERE name=_community_name ), 0 );
+    IF _check AND __community_id = 0 THEN
+      RAISE EXCEPTION 'Community % does not exist', _community_name;
+    END IF;
+  END IF;
+  RETURN __community_id;
+END
+$function$
+;
diff --git a/hive/indexer/blocks.py b/hive/indexer/blocks.py
index 2de572b06..89050cace 100644
--- a/hive/indexer/blocks.py
+++ b/hive/indexer/blocks.py
@@ -266,7 +266,7 @@ class Blocks:
                 elif op_type == 'delete_comment_operation':
                     key = "{}/{}".format(op['author'], op['permlink'])
                     if ( ineffective_deleted_ops is None ) or ( key not in ineffective_deleted_ops ):
-                        Posts.delete_op(op)
+                        Posts.delete_op(op, cls._head_block_date)
                 elif op_type == 'comment_options_operation':
                     Posts.comment_options_op(op)
                 elif op_type == 'vote_operation':
diff --git a/hive/indexer/posts.py b/hive/indexer/posts.py
index 33ec87431..609a12ae8 100644
--- a/hive/indexer/posts.py
+++ b/hive/indexer/posts.py
@@ -78,12 +78,12 @@ class Posts(DbAdapterHolder):
         cls._ids[url] = pid
 
     @classmethod
-    def delete_op(cls, op):
+    def delete_op(cls, op, block_date):
         """Given a delete_comment op, mark the post as deleted.
 
         Also remove it from post-cache and feed-cache.
         """
-        cls.delete(op)
+        cls.delete(op, block_date)
 
     @classmethod
     def comment_op(cls, op, block_date):
@@ -388,14 +388,14 @@ class Posts(DbAdapterHolder):
                  beneficiaries=dumps(beneficiaries))
 
     @classmethod
-    def delete(cls, op):
+    def delete(cls, op, block_date):
         """Marks a post record as being deleted."""
 
         sql = """
               SELECT id, depth
-              FROM delete_hive_post((:author)::varchar, (:permlink)::varchar, (:block_num)::int);
+              FROM delete_hive_post((:author)::varchar, (:permlink)::varchar, (:block_num)::int, (:date)::timestamp);
               """
-        row = DB.query_row(sql, author=op['author'], permlink = op['permlink'], block_num=op['block_num'])
+        row = DB.query_row(sql, author=op['author'], permlink = op['permlink'], block_num=op['block_num'], date=block_date)
 
         result = dict(row)
         pid = result['id']
diff --git a/hive/server/bridge_api/methods.py b/hive/server/bridge_api/methods.py
index c9fb5ef0e..a2bb35e6c 100644
--- a/hive/server/bridge_api/methods.py
+++ b/hive/server/bridge_api/methods.py
@@ -19,8 +19,11 @@ from hive.server.common.mutes import Mutes
 async def get_profile(context, account, observer=None):
     """Load account/profile data."""
     db = context['db']
+    account = valid_account(account)
+    observer = valid_account(observer, allow_empty=True)
+
     ret = await load_profiles(db, [valid_account(account)])
-    assert ret, 'Account \'{}\' does not exist'.format(account)
+    assert ret, 'Account \'{}\' does not exist'.format(account) # should not be needed
 
     observer_id = await get_account_id(db, observer) if observer else None
     if observer_id:
diff --git a/hive/server/database_api/methods.py b/hive/server/database_api/methods.py
index c85de29a4..405da7708 100644
--- a/hive/server/database_api/methods.py
+++ b/hive/server/database_api/methods.py
@@ -4,6 +4,7 @@ from enum import Enum
 from hive.server.common.helpers import return_error_info, valid_limit, valid_account, valid_permlink, valid_date
 from hive.server.database_api.objects import database_post_object
 from hive.server.common.helpers import json_date
+from hive.utils.normalize import escape_characters
 
 @return_error_info
 async def list_comments(context, start: list, limit: int = 1000, order: str = None):
@@ -140,9 +141,10 @@ async def find_comments(context, comments: list):
             hp.author_rewards
         FROM
             hive_posts_view hp
-        JOIN (VALUES {}) AS t (author, permlink) ON hp.author = t.author AND hp.permlink = t.permlink
+        JOIN (VALUES {}) AS t (author, permlink, number) ON hp.author = t.author AND hp.permlink = t.permlink
         WHERE
             NOT hp.is_muted
+        ORDER BY t.number
     """
 
     idx = 0
@@ -156,7 +158,7 @@ async def find_comments(context, comments: list):
             continue
         if idx > 0:
             values += ","
-        values += "('{}','{}')".format(author, permlink) # escaping most likely needed
+        values += "({},{},{})".format(escape_characters(author), escape_characters(permlink), idx)
         idx += 1
     sql = SQL_TEMPLATE.format(values)
 
diff --git a/hive/server/hive_api/common.py b/hive/server/hive_api/common.py
index dd8826789..fe39d4c82 100644
--- a/hive/server/hive_api/common.py
+++ b/hive/server/hive_api/common.py
@@ -14,15 +14,11 @@ def __used_refs():
 
 async def get_community_id(db, name):
     """Get community id from db."""
-    return await db.query_one("SELECT id FROM hive_communities WHERE name = :name",
-                              name=name)
+    return await db.query_one("SELECT find_community_id( (:name)::VARCHAR, True )", name=name)
 
 async def get_account_id(db, name):
     """Get account id from account name."""
-    assert name, 'no account name specified'
-    _id = await db.query_one("SELECT id FROM hive_accounts WHERE name = :n", n=name)
-    assert _id, "account not found: `%s`" % name
-    return _id
+    return await db.query_one("SELECT find_account_id( (:name)::VARCHAR, True )", name=name)
 
 def estimated_sp(vests):
     """Convert VESTS to SP units for display."""
diff --git a/hive/server/hive_api/community.py b/hive/server/hive_api/community.py
index c3828d218..08c2e7705 100644
--- a/hive/server/hive_api/community.py
+++ b/hive/server/hive_api/community.py
@@ -35,11 +35,10 @@ async def get_community(context, name, observer=None):
     """
     db = context['db']
     cid = await get_community_id(db, name)
-    assert cid, 'community not found'
     communities = await load_communities(db, [cid], lite=False)
 
     if observer:
-        valid_account(observer)
+        observer = valid_account(observer)
         observer_id = await get_account_id(db, observer)
         await _append_observer_roles(db, communities, observer_id)
         await _append_observer_subs(db, communities, observer_id)
@@ -50,9 +49,8 @@ async def get_community(context, name, observer=None):
 async def get_community_context(context, name, account):
     """For a community/account: returns role, title, subscribed state"""
     db = context['db']
-    valid_account(account)
+    account = valid_account(account)
     cid = await get_community_id(db, name)
-    assert cid, 'community not found'
 
     aid = await get_account_id(db, account)
     assert aid, 'account not found'
@@ -111,7 +109,7 @@ async def list_pop_communities(context, limit:int=25):
 async def list_all_subscriptions(context, account):
     """Lists all communities `account` subscribes to, plus role and title in each."""
     db = context['db']
-    valid_account(account)
+    account = valid_account(account)
     account_id = await get_account_id(db, account)
 
     sql = """SELECT c.name, c.title, COALESCE(r.role_id, 0), COALESCE(r.title, '')
@@ -183,6 +181,7 @@ async def list_communities(context, last='', limit=100, query=None, sort='rank',
     # append observer context, leadership data
     communities = await load_communities(db, ids, lite=True)
     if observer:
+        observer = valid_account(observer)
         observer_id = await get_account_id(db, observer)
         await _append_observer_subs(db, communities, observer_id)
         await _append_observer_roles(db, communities, observer_id)
@@ -349,11 +348,12 @@ async def top_community_authors(context, community):
 async def top_community_muted(context, community):
     """Get top authors (by SP) who are muted in a community."""
     db = context['db']
+    cid = await get_community_id(db, community)
     sql = """SELECT a.name, a.voting_weight, r.title FROM hive_accounts a
                JOIN hive_roles r ON a.id = r.account_id
               WHERE r.community_id = :community_id AND r.role_id < 0
            ORDER BY voting_weight DESC LIMIT 5"""
-    return await db.query(sql, community_id=await get_community_id(db, community))
+    return await db.query(sql, community_id=cid)
 
 async def _top_community_posts(db, community, limit=50):
     # TODO: muted equivalent
diff --git a/hive/server/hive_api/objects.py b/hive/server/hive_api/objects.py
index f6bb342f9..560269c87 100644
--- a/hive/server/hive_api/objects.py
+++ b/hive/server/hive_api/objects.py
@@ -36,6 +36,7 @@ async def accounts_by_name(db, names, observer=None, lite=True):
         accounts[account['id']] = account
 
     if observer:
+        observer = valid_account(observer)
         await _follow_contexts(db, accounts,
                                observer_id=await get_account_id(db, observer),
                                include_mute=not lite)
diff --git a/hive/server/hive_api/public.py b/hive/server/hive_api/public.py
index 273607a56..0009453ca 100644
--- a/hive/server/hive_api/public.py
+++ b/hive/server/hive_api/public.py
@@ -58,6 +58,7 @@ async def list_following(context, account:str, start:str='', limit:int=50, obser
 async def list_all_muted(context, account):
     """Get a list of all account names muted by `account`."""
     db = context['db']
+    account = valid_account(account)
     sql = """SELECT a.name FROM hive_follows f
                JOIN hive_accounts a ON f.following_id = a.id
               WHERE follower = :follower AND state = 2"""
diff --git a/hive/utils/post_active.py b/hive/utils/post_active.py
index 99e37c470..86a272aca 100644
--- a/hive/utils/post_active.py
+++ b/hive/utils/post_active.py
@@ -6,54 +6,55 @@ DB = Db.instance()
 There are three cases when 'active' field in post is updated:
 1) when a descendant post comment was added (recursivly on any depth)
 2) when a descendant post comment was deleted (recursivly on any depth)
-3) when the post is updated
+3) when the post is updated - that one only updates that post active (not here)
 
 It means that, when the comment for posts is updated then its 'active' field
 does not propagate for its ancestors.
 """
 
 update_active_sql = """
-    WITH RECURSIVE parent_posts ( parent_id, post_id, intrusive_active) AS (
-    	SELECT
-    		parent_id as parent_id,
-    		id as post_id,
-    		CASE WHEN hp1.active = hp1.created_at OR hp1.counter_deleted > 0 THEN hp1.active
-    		ELSE hp1.created_at
-    		END as intrusive_active
-    	FROM hive_posts hp1 {}
-    	UNION
-    	SELECT
-    		hp2.parent_id as parent_id,
-    		id as post_id,
-    		max_time_stamp(
-    			CASE WHEN hp2.active = hp2.created_at OR hp2.counter_deleted > 0 THEN hp2.active
-    			ELSE hp2.created_at
-    			END
-    			, pp.intrusive_active
-    		) as intrusive_active
-    	FROM parent_posts pp
-    	JOIN hive_posts hp2 ON pp.parent_id = hp2.id
-    	WHERE hp2.depth > 0 AND pp.intrusive_active > hp2.active
+    WITH RECURSIVE parent_posts ( parent_id, post_id, intrusive_active ) AS (
+      SELECT
+        hp1.parent_id as parent_id,
+        hp1.id as post_id,
+        CASE WHEN hp1.counter_deleted > 0 THEN hp1.active
+        ELSE hp1.created_at
+        END as intrusive_active
+      FROM hive_posts hp1
+      WHERE hp1.depth > 0 {}
+      UNION
+      SELECT
+        hp2.parent_id as parent_id,
+        hp2.id as post_id,
+        max_time_stamp(
+          CASE WHEN hp2.counter_deleted > 0 THEN hp2.active
+          ELSE hp2.created_at
+          END
+          , pp.intrusive_active
+        ) as intrusive_active
+      FROM parent_posts pp
+      JOIN hive_posts hp2 ON pp.parent_id = hp2.id
+      WHERE hp2.depth > 0
     )
-   UPDATE
-       hive_posts
-   SET
-       active = new_active
-   FROM
-   (
-        SELECT hp.id as post_id, max_time_stamp( hp.active, MAX(pp.intrusive_active)) as new_active
-        FROM parent_posts pp
-        JOIN hive_posts hp ON pp.parent_id = hp.id GROUP BY hp.id
+    UPDATE
+      hive_posts
+    SET
+      active = new_active
+    FROM
+    (
+      SELECT hp.id as post_id, max_time_stamp( hp.active, MAX(pp.intrusive_active) ) as new_active
+      FROM parent_posts pp
+      JOIN hive_posts hp ON pp.parent_id = hp.id GROUP BY hp.id
     ) as dataset
     WHERE dataset.post_id = hive_posts.id;
     """
 
 def update_all_posts_active():
-    DB.query_no_return(update_active_sql.format( "WHERE ( children = 0 OR hp1.counter_deleted > 0 ) AND depth > 0" ))
+    DB.query_no_return(update_active_sql.format( "AND ( hp1.children = 0 )" ))
 
 @time_it
 def update_active_starting_from_posts_on_block( first_block_num, last_block_num ):
     if first_block_num == last_block_num:
-            DB.query_no_return(update_active_sql.format( "WHERE block_num={} AND depth > 0" ).format(first_block_num) )
+            DB.query_no_return(update_active_sql.format( "AND hp1.block_num = {}" ).format(first_block_num) )
             return
-    DB.query_no_return(update_active_sql.format( "WHERE block_num>={} AND block_num <={} AND depth > 0" ).format(first_block_num, last_block_num) )
+    DB.query_no_return(update_active_sql.format( "AND hp1.block_num >= {} AND hp1.block_num <= {}" ).format(first_block_num, last_block_num) )
diff --git a/tests/tests_api b/tests/tests_api
index 10288df4f..70dd4c0e4 160000
--- a/tests/tests_api
+++ b/tests/tests_api
@@ -1 +1 @@
-Subproject commit 10288df4f5217ee9c38b5fb8e8c0b916500a7df9
+Subproject commit 70dd4c0e465e2443ae4c1cb48d7e7dc29341f2dc
-- 
GitLab