Example #1
0
    def _insert_events(
        self,
        c: cursor,
        stored_events: List[StoredEvent],
        **kwargs: Any,
    ) -> None:
        # Acquire "EXCLUSIVE" table lock, to serialize inserts so that
        # insertion of notification IDs is monotonic for notification log
        # readers. We want concurrent transactions to commit inserted
        # SERIAL values in order, and by locking the table for writes,
        # it can be guaranteed. The EXCLUSIVE lock mode does not block
        # the ACCESS SHARE lock which is acquired during SELECT statements,
        # so the table can be read concurrently. However INSERT normally
        # just acquires ROW EXCLUSIVE locks, which risks interleaving of
        # many inserts in one transaction with many insert in another
        # transaction. Since one transaction will commit before another,
        # the possibility arises for readers that are tailing a notification
        # log to miss items inserted later but with lower notification IDs.
        # https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES
        # https://www.postgresql.org/docs/9.1/sql-lock.html
        # https://stackoverflow.com/questions/45866187/guarantee-monotonicity-of
        # -postgresql-serial-column-values-by-commit-order
        if len(stored_events) > 1:
            lock_sqls = [c.mogrify(s) for s in self.lock_statements]
        else:
            lock_sqls = []

        page_size = 500
        batches = []
        params = []
        counter = 0
        for stored_event in stored_events:
            counter += 1
            params.append(
                (
                    stored_event.originator_id,
                    stored_event.originator_version,
                    stored_event.topic,
                    stored_event.state,
                )
            )
            if counter == page_size:
                batches.append(params)
                params = []
                counter = 0
        if params:
            batches.append(params)

        for params in batches:
            sqls = [
                c.mogrify(
                    f"EXECUTE {self.insert_events_statement_name}(%s, %s, %s, %s)", args
                )
                for args in params
            ]
            c.execute(b";".join(chain(lock_sqls, sqls)))
            lock_sqls = []
Example #2
0
    def _insert_events(
        self,
        c: cursor,
        stored_events: List[StoredEvent],
        **kwargs: Any,
    ) -> None:
        # Acquire "EXCLUSIVE" table lock, to serialize inserts so that
        # insertion of notification IDs is monotonic for notification log
        # readers. We want concurrent transactions to commit inserted
        # notification_id values in order, and by locking the table for writes,
        # it can be guaranteed. The EXCLUSIVE lock mode does not block
        # the ACCESS SHARE lock which is acquired during SELECT statements,
        # so the table can be read concurrently. However INSERT normally
        # just acquires ROW EXCLUSIVE locks, which risks interleaving of
        # many inserts in one transaction with many insert in another
        # transaction. Since one transaction will commit before another,
        # the possibility arises for readers that are tailing a notification
        # log to miss items inserted later but with lower notification IDs.
        # https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES
        # https://www.postgresql.org/docs/9.1/sql-lock.html
        # https://stackoverflow.com/questions/45866187/guarantee-monotonicity-of
        # -postgresql-serial-column-values-by-commit-order

        len_stored_events = len(stored_events)

        # Just don't do anything if there is nothing to do.
        if len_stored_events == 0:
            return

        # Mogrify the table lock statements.
        lock_sqls = (c.mogrify(s) for s in self.lock_statements)

        # Prepare the commands before getting the table lock.
        page_size = 500
        pages = [
            (c.mogrify(
                f"EXECUTE {self.insert_events_statement_name}(%s, %s, %s, %s)",
                (
                    stored_event.originator_id,
                    stored_event.originator_version,
                    stored_event.topic,
                    stored_event.state,
                ),
            ) for stored_event in page)
            for page in (stored_events[ndx:min(ndx +
                                               page_size, len_stored_events)]
                         for ndx in range(0, len_stored_events, page_size))
        ]
        commands = [
            b";".join(page)
            for page in chain([chain(lock_sqls, pages[0])], pages[1:])
        ]

        # Execute the commands.
        for command in commands:
            c.execute(command)
Example #3
0
def get_movies_by_ids(ids: List[str], cursor: _cursor) -> List[dict]:
    """
    Retrieves full movies data.
    """
    logger.debug(f"Looking for {len(ids)} movies")
    args = ",".join(cursor.mogrify("%s", (_id, )).decode() for _id in ids)
    cursor.execute(f"""
    SELECT
        fw.id as fw_id, 
        fw.title, 
        fw.description, 
        fw.rating, 
        fw.created_at, 
        fw.updated_at, 
        array_agg(g.name) as genres,
        array_agg(p.full_name) as names,
        array_agg(pfw.role) as roles,
        array_agg(p.id) as persons_ids
    FROM content.film_work fw
    LEFT JOIN content.person_film_work pfw ON pfw.film_work_id = fw.id
    LEFT JOIN content.person p ON p.id = pfw.person_id
    LEFT JOIN content.genre_film_work gfw ON gfw.film_work_id = fw.id
    LEFT JOIN content.genre g ON g.id = gfw.genre_id
    WHERE fw.id IN ({args})
    GROUP BY fw_id;
    """)
    movies = cursor.fetchall()
    logger.debug(f"Found {len(movies)} movies by ids")
    return movies
Example #4
0
def _fetch_movies_by_persons(cursor: _cursor, persons: List[dict],
                             updated_after: datetime.datetime):
    """
    Extracts movies where provided persons participate.
    Also filters movies by updated_at.
    """
    args = ",".join(
        cursor.mogrify("%s", (person["id"], )).decode() for person in persons)
    cursor.execute(
        f"""
                    SELECT fw.id, fw.updated_at 
                    FROM content.film_work fw 
                    LEFT JOIN content.person_film_work pfw ON pfw.film_work_id = fw.id 
                    WHERE updated_at > %s AND pfw.person_id IN ({args}) 
                    ORDER BY fw.updated_at 
                    LIMIT {CONFIG.FETCH_FROM_PG_BY};
                    """, (updated_after, ))

    linked_movies = cursor.fetchall()
    logger.debug(f"Fetched {len(linked_movies)} linked movies")
    return linked_movies
Example #5
0
def _fetch_movies_by_genres(
        cursor: _cursor, genres: List[dict],
        movie_updated_after: datetime.datetime) -> List[dict]:
    """
    Returns all movies related to provided genres list.
    Also filters movies by provided updated_at field.
    """
    args = ",".join(
        cursor.mogrify("%s", (genre["id"], )).decode() for genre in genres)
    cursor.execute(
        f"""
                    SELECT fw.id, fw.updated_at 
                    FROM content.film_work fw 
                    LEFT JOIN content.genre_film_work gfw ON gfw.film_work_id = fw.id 
                    WHERE updated_at > %s AND gfw.genre_id IN ({args}) 
                    ORDER BY fw.updated_at 
                    LIMIT {CONFIG.FETCH_FROM_PG_BY};
                    """, (movie_updated_after, ))

    linked_movies = cursor.fetchall()
    logger.debug(f"Fetched {len(linked_movies)} linked movies")
    return linked_movies