#!/usr/bin/python
# -*- coding: utf-8 -*-

from asyncio import Lock
from sqlite3 import connect, Error, IntegrityError
import sys
import time

DBLOCK = Lock()

class DatabaseSQLite:

    #from slixfeed.log import Logger
    #from slixfeed.utilities import DateAndTime, Url
    
    # DBLOCK = Lock()
    
    #logger = Logger(__name__)
    
    def create_connection(db_file):
        """
        Create a database connection to the SQLite database
        specified by db_file.
    
        Parameters
        ----------
        db_file : str
            Path to database file.
    
        Returns
        -------
        conn : object
            Connection object or None.
        """
        time_begin = time.time()
        function_name = sys._getframe().f_code.co_name
#        message_log = '{}'
#        logger.debug(message_log.format(function_name))
        conn = None
        try:
            conn = connect(db_file)
            conn.execute("PRAGMA foreign_keys = ON")
            # return conn
        except Error as e:
            print(e)
#            logger.warning('Error creating a connection to database {}.'.format(db_file))
#            logger.error(e)
        time_end = time.time()
        difference = time_end - time_begin
        if difference > 1: logger.warning('{} (time: {})'.format(function_name,
                                                                 difference))
        return conn
    
    
    def create_tables(sql_filename, db_file):
        """
        Create SQLite tables.
    
        Parameters
        ----------
        db_file : str
            Path to database file.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        with DatabaseSQLite.create_connection(db_file) as conn:
            # Read the SQL script from the file
            with open(sql_filename, 'r') as sql_file:
                sql_script = sql_file.read()
            cur = conn.cursor()
            # Execute the SQL script
            try:
                cur.executescript(sql_script)
                print("Table created successfully.")
            except sqlite3.Error as e:
                print(f"An error occurred: {e}")

    async def associate_entries_tags_jids(db_file, entry):
        async with DBLOCK:
            with DatabaseSQLite.create_connection(db_file) as conn:
                cur = conn.cursor()
                jid = entry['jid']
                url_hash = entry['url_hash']
                entry_id = DatabaseSQLite.get_entry_id_by_url_hash(db_file, url_hash)
                jid_id = DatabaseSQLite.get_jid_id_by_jid(db_file, jid)
                if entry_id:
                    for tag in entry['tags']:
                        tag_id = DatabaseSQLite.get_tag_id_by_tag(db_file, tag)
                        cet_id = DatabaseSQLite.get_combination_id_by_entry_id_tag_id_jid_id(db_file, entry_id, tag_id, jid_id)
                        if not cet_id:
                            sql = (
                                """
                                INSERT
                                INTO combination_entries_tags_jids (
                                    entry_id, tag_id, jid_id)
                                VALUES (
                                    ?, ?, ?);
                                """
                                )
                            par = (entry_id, tag_id, jid_id)
                            try:
                                cur.execute(sql, par)
                            except IntegrityError as e:
                                print('associate_entries_tags_jids')
                                print(e)

    async def add_tags(db_file, entries):
        """
        Batch insertion of tags.
    
        Parameters
        ----------
        db_file : str
            Path to database file.
        entries : list
            Set of entries.

        Returns
        -------
        None.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        async with DBLOCK:
            with DatabaseSQLite.create_connection(db_file) as conn:
                cur = conn.cursor()
                for entry in entries:
                    tags = entry['tags']
                    for tag in tags:
#                        sql = (
#                            """
#                            INSERT OR IGNORE INTO main_tags(tag) VALUES (?);
#                            """
#                            )
                        if not DatabaseSQLite.get_tag_id_by_tag(db_file, tag):
                            sql = (
                                """
                                INSERT INTO main_tags(tag) VALUES(?);
                                """
                                )
                            par = (tag,)
                            try:
                                cur.execute(sql, par)
                            except IntegrityError as e:
                                print(e)

    async def add_new_entries(db_file, entries):
        """
        Batch insert of new entries into table entries.
    
        Parameters
        ----------
        db_file : str
            Path to database file.
        entries : list
            Set of entries.

        Returns
        -------
        None.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        async with DBLOCK:

            with DatabaseSQLite.create_connection(db_file) as conn:
                cur = conn.cursor()

                for entry in entries:
                    url_hash = entry['url_hash']
                    url = entry['link']
                    title = entry['title']
                    summary = entry['summary']
                    jid = entry['jid']
                    date_first = entry['published']
                    date_last = entry['published']
    #               instances = entry['instances']

                    # Import entries
                    jid_id = DatabaseSQLite.get_jid_id_by_jid(db_file, jid)
                    sql = (
                        """
                        INSERT
                        INTO main_entries(
                            url_hash, url, title, summary, jid_id, date_first, date_last)
                        VALUES(
                            ?, ?, ?, ?, ?, ?, ?);
                        """
                        )
                    par = (url_hash, url, title, summary, jid_id, date_first, date_last)

                    try:
                        cur.execute(sql, par)
                    except IntegrityError as e:
                        print(e)
                        print(jid_id)
                        print(entry)
#                        logger.warning("Skipping: " + str(url))
#                        logger.error(e)

    # TODO An additional function to ssociate jid_id (jid) with entry_id (hash_url)
    async def set_jid(db_file, jid):
        """
        Add a JID to database.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            A Jabber ID.

        Returns
        -------
        None.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {}'
#                    .format(function_name, db_file, jid))
        sql = (
            """
            INSERT
            INTO main_jids(
                jid)
            VALUES(
                ?);
            """
            )
        par = (jid, )
        async with DBLOCK:
            with DatabaseSQLite.create_connection(db_file) as conn:
                cur = conn.cursor()
                try:
                    cur.execute(sql, par)
                except IntegrityError as e:
                    print(e)
#                            logger.warning("Skipping: " + str(url))
#                            logger.error(e)

    def get_entries_count(db_file):
        """
        Get entries count.

        Parameters
        ----------
        db_file : str
            Path to database file.

        Returns
        -------
        result : tuple
            Number.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        sql = (
            """
            SELECT count
            FROM main_statistics
            WHERE type = "entries";
            """
            )
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_combination_id_by_entry_id_tag_id_jid_id(db_file, entry_id, tag_id, jid_id):
        """
        Get ID by a given Entry ID and a given Tag ID and a given Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        entry_id : str
            Entry ID.
        tag_id : str
            Tag ID.
        jid_id : str
            Jabber ID.

        Returns
        -------
        result : tuple
            ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} entry_id: {} tag_id: {} jid_id: {}'
#                    .format(function_name, db_file, entry_id, tag_id, jid_id))
        sql = (
            """
            SELECT id
            FROM combination_entries_tags_jids
            WHERE entry_id = :entry_id AND tag_id = :tag_id AND jid_id = :jid_id;
            """
            )
        par = {
            "entry_id": entry_id,
            "tag_id": tag_id,
            "jid_id": jid_id
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    async def delete_combination_row_by_url_hash_and_tag_and_jid(db_file, url_hash, tags, jid):
        """
        Delete a row by a given entry ID and a given Jabber ID and given tags.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            URL hash.
        tags : list
            Tags.
        jid : str
            Jabber ID.

        Returns
        -------
        None.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} url_hash: {} tag_id: {} jid_id: {}'
#                    .format(function_name, db_file, url_hash, tag_id, jid_id))
        sql = (
            """
            DELETE
            FROM combination_entries_tags_jids
            WHERE
                entry_id = (SELECT id FROM main_entries WHERE url_hash = :url_hash) AND
                tag_id = (SELECT id FROM main_tags WHERE tag = :tag) AND
                jid_id = (SELECT id FROM main_jids WHERE jid = :jid);
            """
            )
        async with DBLOCK:
            with DatabaseSQLite.create_connection(db_file) as conn:
                for tag in tags:
                    par = {
                        "url_hash": url_hash,
                        "tag": tag,
                        "jid": jid
                        }
                    cur = conn.cursor()
                    cur.execute(sql, par)

    def get_tag_id_and_instances_by_tag(db_file, tag):
        """
        Get a tag ID and instances by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            Tag.

        Returns
        -------
        result : tuple
            Tag ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT id, instances
            FROM main_tags
            WHERE tag = ?;
            """
            )
        par = (tag,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
#            return result[0] if result else None, None
            if not result: result = None, None
            return result

    def get_tags_and_instances_by_url_hash(db_file, url_hash):
        """
        Get tags and instances by a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            A hash of a URL.

        Returns
        -------
        result : tuple
            Tags and instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT mt.tag, mt.instances
            FROM main_tags AS mt
            INNER JOIN combination_entries_tags_jids AS co ON mt.id = co.tag_id
            INNER JOIN main_entries AS me ON me.id = co.entry_id
            WHERE me.url_hash = ?
            ORDER BY mt.instances DESC;
            """
            )
        par = (url_hash,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_tags_and_instances_by_entry_id(db_file, entry_id):
        """
        Get tags and instances by a given ID entry.

        Parameters
        ----------
        db_file : str
            Path to database file.
        entry_id : str
            An ID of an entry.

        Returns
        -------
        result : tuple
            Tags and instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT main_tags.tag, main_tags.instances
            FROM main_tags
            INNER JOIN combination_entries_tags_jids ON main_tags.id = combination_entries_tags_jids.tag_id
            WHERE combination_entries_tags_jids.entry_id = ?
            ORDER BY main_tags.instances DESC;
            """
            )
        par = (entry_id,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_jids_and_tags_by_entry_id(db_file, entry_id):
        """
        Get JIDs and tags by a given ID entry.

        Parameters
        ----------
        db_file : str
            Path to database file.
        entry_id : str
            An ID of an entry.

        Returns
        -------
        result : tuple
            JIDs and tags.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT main_jids.jid, main_tags.tag
            FROM main_tags
            INNER JOIN combination_entries_tags_jids ON main_tags.id = combination_entries_tags_jids.tag_id
            INNER JOIN main_jids ON main_jids.id = combination_entries_tags_jids.jid_id
            WHERE combination_entries_tags_jids.entry_id = ?
            ORDER BY main_tags.instances DESC;
            """
            )
        par = (entry_id,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_jids_and_tags_by_url_hash(db_file, url_hash):
        """
        Get JIDs and tags by a given URI hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            A URL hash of an entry.

        Returns
        -------
        result : tuple
            JIDs and tags.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT main_jids.jid, main_tags.tag
            FROM main_tags
            INNER JOIN combination_entries_tags_jids ON main_tags.id = combination_entries_tags_jids.tag_id
            INNER JOIN main_jids ON main_jids.id = combination_entries_tags_jids.jid_id
            INNER JOIN main_entries ON main_entries.id = combination_entries_tags_jids.entry_id
            WHERE main_entries.url_hash = ?
            ORDER BY main_tags.instances DESC;
            """
            )
        par = (url_hash,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_tag_id_by_tag(db_file, tag):
        """
        Get a tag ID by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            Tag.

        Returns
        -------
        result : tuple
            Tag ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT id
            FROM main_tags
            WHERE tag = ?;
            """
            )
        par = (tag,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entry_id_by_url_hash(db_file, url_hash):
        """
        Get an entry ID by a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            MD5 hash of URL.

        Returns
        -------
        result : tuple
            Entry ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} url_hash: {}'
#                    .format(function_name, db_file, url_hash))
        sql = (
            """
            SELECT id
            FROM main_entries
            WHERE url_hash = ?;
            """
            )
        par = (url_hash,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entry_instances_by_url_hash(db_file, url_hash):
        """
        Get value of entry instances by a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            MD5 hash of URL.

        Returns
        -------
        result : tuple
            Value of entry instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} url_hash: {}'
#                    .format(function_name, db_file, url_hash))
        sql = (
            """
            SELECT instances
            FROM main_entries
            WHERE url_hash = ?;
            """
            )
        par = (url_hash,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entry_by_url_hash(db_file, url_hash):
        """
        Get entry of a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        url_hash : str
            MD5 hash of URL.

        Returns
        -------
        result : tuple
            Entry properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} url_hash: {}'
#                    .format(function_name, db_file, url_hash))
        sql = (
            """
            SELECT *
            FROM main_entries
            WHERE url_hash = ?;
            """
            )
        par = (url_hash,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_new(db_file, index_first):
        """
        Get new entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT *
            FROM main_entries
            ORDER BY date_first DESC
            LIMIT 10
            OFFSET ?;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_popular(db_file, index_first):
        """
        Get popular entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT *
            FROM main_entries
            ORDER BY instances DESC
            LIMIT 10
            OFFSET ?;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_recent(db_file, index_first):
        """
        Get recent entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT *
            FROM main_entries
            ORDER BY date_last DESC
            LIMIT 10
            OFFSET ?;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_by_query(db_file, query, index_first):
        """
        Get entries by a query.

        Parameters
        ----------
        db_file : str
            Path to database file.
        query : str
            Search query.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT *
            FROM main_entries
            WHERE title LIKE :query OR url LIKE :query OR summary LIKE :query
            ORDER BY instances DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "query": f'%{query}%',
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_count_by_query(db_file, query):
        """
        Get entries count by a query.

        Parameters
        ----------
        db_file : str
            Path to database file.
        query : str
            Search query.

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        # NOTE Consider date_first
        sql = (
            """
            SELECT COUNT(id)
            FROM main_entries
            WHERE title LIKE :query OR url LIKE :query OR summary LIKE :query
            ORDER BY instances DESC;
            """
            )
        par = {
            "query": f'%{query}%',
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_by_jid_and_tag(db_file, jid, tag, index_first):
        """
        Get entries by a tag and a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            Tag.
        jid : str
            Jabber ID.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {} jid: {} index_first: {}'
#                    .format(function_name, db_file, tag, jid, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT DISTINCT me.*
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE mj.jid = :jid AND mt.tag = :tag
            ORDER BY instances DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "jid": jid,
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_count_by_jid_and_tag(db_file, jid, tag):
        """
        Get entries count by a tag and a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            Tag.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {} jid: {}'
#                    .format(function_name, db_file, tag, jid))
        # NOTE Consider date_first
        sql = (
            """
            SELECT COUNT(DISTINCT me.id)
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE mj.jid = :jid AND mt.tag = :tag;
            """
            )
        par = {
            "jid": jid,
            "tag": tag
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_by_jid_and_query(db_file, jid, query, index_first):
        """
        Get entries by a query and a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        query : str
            Search query.
        jid : str
            Jabber ID.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} query: {} jid: {} index_first: {}'
#                    .format(function_name, db_file, query, jid, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT DISTINCT me.*
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            WHERE mj.jid = :jid AND (title LIKE :query OR url LIKE :query OR summary LIKE :query)
            ORDER BY instances DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "jid": jid,
            "query": f'%{query}%',
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_count_by_jid_and_query(db_file, jid, query):
        """
        Get entries count by a query and a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        query : str
            Search query.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} query: {} jid: {}'
#                    .format(function_name, db_file, query, jid))
        # NOTE Consider date_first
        sql = (
            """
            SELECT COUNT(DISTINCT me.id)
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            WHERE mj.jid = :jid AND (title LIKE :query OR url LIKE :query OR summary LIKE :query);
            """
            )
        par = {
            "jid": jid,
            "query": f'%{query}%'
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_by_jid(db_file, jid, index_first):
        """
        Get entries by a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {} index_first: {}'
#                    .format(function_name, db_file, jid, index_first))
        # NOTE Consider date_first
        sql = (
            """
            SELECT DISTINCT me.*
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            WHERE mj.jid = :jid
            ORDER BY instances DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "jid": jid,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_count_by_jid(db_file, jid):
        """
        Get entries count by a Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Entries properties.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {}'
#                    .format(function_name, db_file, jid))
        # NOTE Consider date_first
        sql = (
            """
            SELECT COUNT(DISTINCT me.id)
            FROM main_entries AS me
            INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
            INNER JOIN main_jids AS mj ON mj.id = co.jid_id
            WHERE mj.jid = :jid;
            """
            )
        par = {
            "jid": jid
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_count_by_tag(db_file, tag):
        """
        Get entries count by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            A tag.

        Returns
        -------
        result : tuple
            Entries.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT COUNT(DISTINCT entries.id)
            FROM main_entries AS entries
            INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
            INNER JOIN main_tags AS tags ON tags.id = co.tag_id
            WHERE tags.tag = :tag;
            """
            )
        par = (tag,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_entries_popular_by_tag(db_file, tag, index_first):
        """
        Get popular entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            A tag.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT DISTINCT entries.*
            FROM main_entries AS entries
            INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
            INNER JOIN main_tags AS tags ON tags.id = co.tag_id
            WHERE tags.tag = :tag
            ORDER BY entries.instances DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_recent_by_tag(db_file, tag, index_first):
        """
        Get recent entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            A tag.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT DISTINCT entries.*
            FROM main_entries AS entries
            INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
            INNER JOIN main_tags AS tags ON tags.id = co.tag_id
            WHERE tags.tag = :tag
            ORDER BY date_last DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_entries_new_by_tag(db_file, tag, index_first):
        """
        Get new entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        tag : str
            A tag.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Entries.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} tag: {}'
#                    .format(function_name, db_file, tag))
        sql = (
            """
            SELECT DISTINCT entries.*
            FROM main_entries AS entries
            INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
            INNER JOIN main_tags AS tags ON tags.id = co.tag_id
            WHERE tags.tag = :tag
            ORDER BY date_first DESC
            LIMIT 10
            OFFSET :index_first;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_tags_30(db_file):
        """
        Get 30 tags.

        Parameters
        ----------
        db_file : str
            Path to database file.

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT tag, instances
            FROM main_tags
            ORDER BY instances DESC
            LIMIT 30;
            """
            )
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql).fetchall()
            return result

    def get_30_tags_by_entries_popular(db_file, index_first):
        """
        Get 30 tags by currently viewed popular entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT id
                FROM main_entries
                ORDER BY instances DESC
                LIMIT 10
                OFFSET ?
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_new_by_tag(db_file, tag, index_first):
        """
        Get 30 tags by currently viewed new entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT DISTINCT entries.id
                FROM main_entries AS entries
                INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
                INNER JOIN main_tags AS tags ON tags.id = co.tag_id
                WHERE tags.tag = :tag
                ORDER BY date_first DESC
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_popular_by_tag(db_file, tag, index_first):
        """
        Get 30 tags by currently viewed popular entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT DISTINCT entries.id
                FROM main_entries AS entries
                INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
                INNER JOIN main_tags AS tags ON tags.id = co.tag_id
                WHERE tags.tag = :tag
                ORDER BY entries.instances DESC
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_recent_by_tag(db_file, tag, index_first):
        """
        Get 30 tags by currently viewed recent entries by a given tag.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT DISTINCT entries.id
                FROM main_entries AS entries
                INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
                INNER JOIN main_tags AS tags ON tags.id = co.tag_id
                WHERE tags.tag = :tag
                ORDER BY date_last DESC
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_new(db_file, index_first):
        """
        Get 30 tags by currently viewed new entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT id
                FROM main_entries
                ORDER BY date_first DESC
                LIMIT 10
                OFFSET ?
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_recent(db_file, index_first):
        """
        Get 30 tags by currently viewed recent entries.

        Parameters
        ----------
        db_file : str
            Path to database file.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT id
                FROM main_entries
                ORDER BY date_last DESC
                LIMIT 10
                OFFSET ?
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = (index_first,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_entries_by_query_recent(db_file, query, index_first):
        """
        Get 30 tags by currently viewed entries by query.

        Parameters
        ----------
        db_file : str
            Path to database file.
        query : str
            A search query.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT id
                FROM main_entries
                WHERE title LIKE :query OR url LIKE :query OR summary LIKE :query
                ORDER BY instances DESC
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "query": f'%{query}%',
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_jid_and_tag(db_file, jid, tag, index_first):
        """
        Get 30 tags by Jabber ID and tags.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.
        tag : str
            A tag.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT co.entry_id
                FROM main_entries AS me
                INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
                INNER JOIN main_jids AS mj ON mj.id = co.jid_id
                INNER JOIN main_tags AS mt ON mt.id = co.tag_id
                WHERE mj.jid = :jid AND mt.tag = :tag
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "jid": jid,
            "tag": tag,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_jid_and_query(db_file, jid, query, index_first):
        """
        Get 30 tags by Jabber ID and query.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.
        query : str
            A search query.
        index_first : str
            .

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT co.entry_id
                FROM main_entries AS me
                INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
                INNER JOIN main_jids AS mj ON mj.id = co.jid_id
                INNER JOIN main_tags AS mt ON mt.id = co.tag_id
                WHERE mj.jid = :jid AND (title LIKE :query OR url LIKE :query OR summary LIKE :query)
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "jid": jid,
            "query": f'%{query}%',
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_30_tags_by_jid(db_file, jid, index_first):
        """
        Get 30 tags by Jabber ID.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT mt.tag, mt.instances
            FROM combination_entries_tags_jids AS co
            INNER JOIN main_tags AS mt ON mt.id = co.tag_id
            WHERE co.entry_id IN (
                SELECT DISTINCT me.id
                FROM main_entries AS me
                INNER JOIN combination_entries_tags_jids AS co ON co.entry_id = me.id
                INNER JOIN main_jids AS mj ON mj.id = co.jid_id
                WHERE mj.jid = :jid
                ORDER BY instances DESC
                LIMIT 10
                OFFSET :index_first
            )
            ORDER BY mt.instances DESC
            LIMIT 30;
            """
            )
        par = {
            "jid": jid,
            "index_first": index_first
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_tags_500(db_file):
        """
        Get 500 tags.

        Parameters
        ----------
        db_file : str
            Path to database file.

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {}'
#                    .format(function_name, db_file))
        sql = (
            """
            WITH Common500Tags AS (
                SELECT tag, instances
                FROM main_tags
                ORDER BY instances DESC
                LIMIT 500
            )
            SELECT tag, instances
            FROM Common500Tags
            ORDER BY tag ASC;
            """
            )
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql).fetchall()
            return result

    def get_500_tags_by_jid_sorted_by_name(db_file, jid):
        """
        Get 500 tags by Jabber ID, sorted by name.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT mt.tag, COUNT(*) AS instances
            FROM main_tags mt
            JOIN combination_entries_tags_jids combination ON mt.id = combination.tag_id
            JOIN main_jids mj ON combination.jid_id = mj.id
            WHERE mj.jid = :jid
            GROUP BY mt.tag
            LIMIT 500;
            """
            )
        par = {
            "jid": jid
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_500_tags_by_jid_sorted_by_instance(db_file, jid):
        """
        Get 500 tags by Jabber ID, sorted by instance.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            Tags and number of instances.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT mt.tag, COUNT(*) AS instances
            FROM main_tags mt
            JOIN combination_entries_tags_jids combination ON mt.id = combination.tag_id
            JOIN main_jids mj ON combination.jid_id = mj.id
            WHERE mj.jid = :jid
            GROUP BY mt.tag
            ORDER BY instances DESC
            LIMIT 500;
            """
            )
        par = {
            "jid": jid
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    # FIXME It appear that the wrong table is fetched
    # The table to be fetched is combination_entries_tags_jids
    def is_jid_associated_with_url_hash(db_file, jid, url_hash):
        """
        Check whether a given Jabber ID is associated with a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            A Jabber ID.
        url_hash : str
            An MD5 checksuum of a URL.

        Returns
        -------
        result : tuple
            Tags.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {} url_hash: {}'
#                    .format(function_name, db_file, jid, url_hash))
        sql = (
            """
            SELECT mj.jid, me.url_hash
            FROM main_jids AS mj
            INNER JOIN combination_entries_tags_jids AS co ON mj.id = co.jid_id
            INNER JOIN main_entries AS me ON me.id = co.entry_id
            WHERE mj.jid = :jid AND me.url_hash = :url_hash;
            """
            )
        par = {
            "jid": jid,
            "url_hash": url_hash
            }
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    #deassociate_entry_from_jid
    async def delete_combination_row_by_jid_and_url_hash(db_file, url_hash, jid):
        """
        Remove association of a given Jabber ID and a given URL hash.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            A Jabber ID.
        url_hash : str
            An MD5 checksuum of a URL.

        Returns
        -------
        None.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {} url_hash: {}'
#                    .format(function_name, db_file, jid, url_hash))
        sql = (
            """
            DELETE FROM combination_entries_tags_jids
            WHERE id IN (
                SELECT co.id
                FROM combination_entries_tags_jids co
                JOIN main_entries me ON co.entry_id = me.id
                JOIN main_jids mj ON co.jid_id = mj.id
                WHERE me.url_hash = :url_hash AND mj.jid = :jid
            );
            """
            )
        par = {
            "jid": jid,
            "url_hash": url_hash
            }
        async with DBLOCK:
            with DatabaseSQLite.create_connection(db_file) as conn:
                cur = conn.cursor()
                cur.execute(sql, par)

    # NOTE The result was ordered by number of instances
    #      ORDER BY main_tags.instances DESC
    #      And has been changed to order of alphabet
    #      ORDER BY main_tags.tag ASC
    def get_tags_by_entry_id(db_file, entry_id):
        """
        Get tags by an ID entry.

        Parameters
        ----------
        db_file : str
            Path to database file.
        entry_id : str
            An ID of an entry.

        Returns
        -------
        result : tuple
            Tags.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} index_first: {}'
#                    .format(function_name, db_file, index_first))
        sql = (
            """
            SELECT DISTINCT main_tags.tag
            FROM main_tags
            INNER JOIN combination_entries_tags_jids ON main_tags.id = combination_entries_tags_jids.tag_id
            WHERE combination_entries_tags_jids.entry_id = ?
            ORDER BY main_tags.tag ASC
            LIMIT 5;
            """
            )
        par = (entry_id,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchall()
            return result

    def get_jid_id_by_jid(db_file, jid):
        """
        Get id of a given jid.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid : str
            Jabber ID.

        Returns
        -------
        result : tuple
            ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid: {}'
#                    .format(function_name, db_file, jid))
        sql = (
            """
            SELECT id
            FROM main_jids
            WHERE jid = ?;
            """
            )
        par = (jid,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result

    def get_jid_by_jid_id(db_file, jid_id):
        """
        Get jid of a given jid_id.

        Parameters
        ----------
        db_file : str
            Path to database file.
        jid_id : str
            ID of Jabber ID.

        Returns
        -------
        result : tuple
            ID.
        """
        function_name = sys._getframe().f_code.co_name
#        logger.debug('{}: db_file: {} jid_id: {}'
#                    .format(function_name, db_file, jid_id))
        sql = (
            """
            SELECT jid
            FROM main_jids
            WHERE id = ?;
            """
            )
        par = (jid_id,)
        with DatabaseSQLite.create_connection(db_file) as conn:
            cur = conn.cursor()
            result = cur.execute(sql, par).fetchone()
            return result[0] if result and len(result) == 1 else result