示例#1
0
    def test_select(self):
        recorder = SQLiteProcessRecorder(SQLiteDatastore(":memory:"))
        recorder.create_table()

        # Construct notification log.
        notification_log = LocalNotificationLog(recorder, section_size=5)
        reader = NotificationLogReader(notification_log, section_size=5)
        notifications = list(reader.select(start=1))
        self.assertEqual(len(notifications), 0)

        # Write 5 events.
        originator_id = uuid4()
        for i in range(5):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events(
                [stored_event],
            )

        notifications = list(reader.select(start=1))
        self.assertEqual(len(notifications), 5)

        # Write 4 events.
        originator_id = uuid4()
        for i in range(4):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events([stored_event])

        notifications = list(reader.select(start=1))
        self.assertEqual(len(notifications), 9)

        notifications = list(reader.select(start=2))
        self.assertEqual(len(notifications), 8)

        notifications = list(reader.select(start=3))
        self.assertEqual(len(notifications), 7)

        notifications = list(reader.select(start=4))
        self.assertEqual(len(notifications), 6)

        notifications = list(reader.select(start=8))
        self.assertEqual(len(notifications), 2)

        notifications = list(reader.select(start=9))
        self.assertEqual(len(notifications), 1)

        notifications = list(reader.select(start=10))
        self.assertEqual(len(notifications), 0)
示例#2
0
        def insert_events() -> None:
            thread_id = get_ident()
            if thread_id not in threads:
                threads[thread_id] = len(threads)
            if thread_id not in counts:
                counts[thread_id] = 0
            if thread_id not in durations:
                durations[thread_id] = 0

            originator_id = uuid4()
            stored_events = [
                StoredEvent(
                    originator_id=originator_id,
                    originator_version=i,
                    topic="topic",
                    state=b"state",
                ) for i in range(NUM_EVENTS)
            ]

            try:
                recorder.insert_events(stored_events)

            except Exception:  # pragma: nocover
                errors_happened.set()
                tb = traceback.format_exc()
                print(tb)
            finally:
                ended = datetime.now()
                duration = (ended - started).total_seconds()
                counts[thread_id] += 1
                durations[thread_id] = duration
示例#3
0
 def create_stack(self, originator_id):
     return [
         StoredEvent(
             originator_id=originator_id,
             originator_version=i,
             topic="",
             state=b"",
         ) for i in range(self.insert_num)
     ]
示例#4
0
        def insert() -> None:
            originator_id = uuid4()

            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=0,
                topic="topic1",
                state=b"state1",
            )
            recorder.insert_events([stored_event])
示例#5
0
 def test_insert_raises_operational_error_if_table_not_created(self):
     recorder = SQLiteProcessRecorder(SQLiteDatastore(":memory:"))
     stored_event1 = StoredEvent(
         originator_id=uuid4(),
         originator_version=1,
         topic="topic1",
         state=b"",
     )
     with self.assertRaises(OperationalError):
         recorder.insert_events([stored_event1])
示例#6
0
    def select_events(
        self,
        originator_id: UUID,
        gt: Optional[int] = None,
        lte: Optional[int] = None,
        desc: bool = False,
        limit: Optional[int] = None,
    ) -> List[StoredEvent]:
        parts = [self.select_events_statement]
        params: List[Any] = [originator_id]
        statement_name = f"select_{self.events_table_name}".replace(".", "_")
        if gt is not None:
            params.append(gt)
            parts.append(f"AND originator_version > ${len(params)}")
            statement_name += "_gt"
        if lte is not None:
            params.append(lte)
            parts.append(f"AND originator_version <= ${len(params)}")
            statement_name += "_lte"
        parts.append("ORDER BY originator_version")
        if desc is False:
            parts.append("ASC")
        else:
            parts.append("DESC")
            statement_name += "_desc"
        if limit is not None:
            params.append(limit)
            parts.append(f"LIMIT ${len(params)}")
            statement_name += "_limit"
        statement = " ".join(parts)

        stored_events = []

        with self.datastore.get_connection() as conn:
            alias = self._prepare(conn, statement_name, statement)

            with conn.transaction(commit=False) as curs:
                curs.execute(
                    f"EXECUTE {alias}({', '.join(['%s' for _ in params])})",
                    params,
                )
                for row in curs.fetchall():
                    stored_events.append(
                        StoredEvent(
                            originator_id=row["originator_id"],
                            originator_version=row["originator_version"],
                            topic=row["topic"],
                            state=bytes(row["state"]),
                        ))
                pass  # for Coverage 5.5 bug with CPython 3.10.0rc1
            return stored_events
示例#7
0
    def test_insert_events_raises_programming_error_when_table_not_created(
            self):
        # Construct the recorder.
        recorder = self.create_recorder()

        # Write a stored event without creating the table.
        stored_event1 = StoredEvent(
            originator_id=uuid4(),
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        with self.assertRaises(ProgrammingError):
            recorder.insert_events([stored_event1])
示例#8
0
    def test_insert_events_raises_programming_error_when_sql_is_broken(self):
        # Construct the recorder.
        recorder = self.create_recorder()

        # Create the table.
        recorder.create_table()

        # Write a stored event with broken statement.
        recorder.insert_events_statement = "BLAH"
        stored_event1 = StoredEvent(
            originator_id=uuid4(),
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        with self.assertRaises(ProgrammingError):
            recorder.insert_events([stored_event1])
示例#9
0
    def test_retry_insert_events_after_closing_connection(self):
        # Construct the recorder.
        recorder = self.create_recorder()

        # Check we have a connection (from create_table).
        self.assertTrue(self.datastore._connections)

        # Close connections.
        pg_close_all_connections()

        # Write a stored event.
        stored_event1 = StoredEvent(
            originator_id=uuid4(),
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        recorder.insert_events([stored_event1])
        def _createevent():
            thread_id = get_ident()
            if thread_id not in threads:
                threads[thread_id] = len(threads)
            if thread_id not in counts:
                counts[thread_id] = 0
            if thread_id not in durations:
                durations[thread_id] = 0

            # thread_num = threads[thread_id]
            # count = counts[thread_id]

            originator_id = uuid4()
            stored_events = [
                StoredEvent(
                    originator_id=originator_id,
                    originator_version=i,
                    topic="topic",
                    state=b"state",
                ) for i in range(100)
            ]
            started = datetime.now()
            # print(f"Thread {thread_num} write beginning #{count + 1}")
            try:
                recorder.insert_events(stored_events)

            except Exception:
                ended = datetime.now()
                duration = (ended - started).total_seconds()
                print(f"Error after starting {duration}")
                errors_happened.set()
                tb = traceback.format_exc()
                print(tb)
                pass
            else:
                return "OK"
            finally:
                ended = datetime.now()
                duration = (ended - started).total_seconds()
                counts[thread_id] += 1
                if duration > durations[thread_id]:
                    durations[thread_id] = duration
示例#11
0
    def test_raises_operational_error_when_inserting_fails(self):
        # Construct the recorder.
        recorder = PostgresAggregateRecorder(datastore=self.datastore,
                                             events_table_name="stored_events")
        recorder.create_table()

        # Mess up the statement.
        recorder.insert_events_statement = "BLAH"

        # Write two stored events.
        originator_id = uuid4()

        stored_event1 = StoredEvent(
            originator_id=originator_id,
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        with self.assertRaises(OperationalError):
            recorder.insert_events([stored_event1])
        def insert():
            originator_id = uuid4()

            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=0,
                topic="topic1",
                state=b"state1",
            )
            tracking1 = Tracking(
                application_name="upstream_app",
                notification_id=next(notification_ids),
            )

            recorder.insert_events(
                stored_events=[
                    stored_event,
                ],
                tracking=tracking1,
            )
示例#13
0
 def select_events(
     self,
     originator_id: UUID,
     gt: Optional[int] = None,
     lte: Optional[int] = None,
     desc: bool = False,
     limit: Optional[int] = None,
 ) -> List[StoredEvent]:
     statement = self.select_events_statement
     params: List[Any] = [originator_id]
     if gt is not None:
         statement += "AND originator_version > %s "
         params.append(gt)
     if lte is not None:
         statement += "AND originator_version <= %s "
         params.append(lte)
     statement += "ORDER BY originator_version "
     if desc is False:
         statement += "ASC "
     else:
         statement += "DESC "
     if limit is not None:
         statement += "LIMIT %s "
         params.append(limit)
     # statement += ";"
     stored_events = []
     try:
         with self.datastore.transaction() as c:
             c.execute(statement, params)
             for row in c.fetchall():
                 stored_events.append(
                     StoredEvent(
                         originator_id=row["originator_id"],
                         originator_version=row["originator_version"],
                         topic=row["topic"],
                         state=bytes(row["state"]),
                     )
                 )
     except psycopg2.Error as e:
         raise OperationalError(e)
     return stored_events
示例#14
0
        def insert_events() -> None:
            thread_id = get_ident()
            if thread_id not in threads:
                threads[thread_id] = len(threads)
            if thread_id not in counts:
                counts[thread_id] = 0
            if thread_id not in durations:
                durations[thread_id] = 0

            # thread_num = threads[thread_id]
            # count = counts[thread_id]

            originator_id = uuid4()
            stored_events = [
                StoredEvent(
                    originator_id=originator_id,
                    originator_version=i,
                    topic="topic",
                    state=b"state",
                ) for i in range(num_events_per_write)
            ]
            started = datetime.now()
            # print(f"Thread {thread_num} write beginning #{count + 1}")
            try:
                recorder.insert_events(stored_events)

            except Exception as e:  # pragma: nocover
                if errors:
                    return
                ended = datetime.now()
                duration = (ended - started).total_seconds()
                print(f"Error after starting {duration}", e)
                errors.append(e)
            else:
                ended = datetime.now()
                duration = (ended - started).total_seconds()
                counts[thread_id] += 1
                if duration > durations[thread_id]:
                    durations[thread_id] = duration
                sleep(writer_sleep)
示例#15
0
 def select_events(
     self,
     originator_id: UUID,
     gt: Optional[int] = None,
     lte: Optional[int] = None,
     desc: bool = False,
     limit: Optional[int] = None,
 ) -> List[StoredEvent]:
     statement = self.select_events_statement
     params: List[Any] = [originator_id.hex]
     if gt is not None:
         statement += "AND originator_version>? "
         params.append(gt)
     if lte is not None:
         statement += "AND originator_version<=? "
         params.append(lte)
     statement += "ORDER BY originator_version "
     if desc is False:
         statement += "ASC "
     else:
         statement += "DESC "
     if limit is not None:
         statement += "LIMIT ? "
         params.append(limit)
     try:
         c = self.datastore.get_connection().cursor()
         c.execute(statement, params)
         stored_events = []
         for row in c.fetchall():
             stored_events.append(
                 StoredEvent(
                     originator_id=UUID(row["originator_id"]),
                     originator_version=row["originator_version"],
                     topic=row["topic"],
                     state=row["state"],
                 ))
     except sqlite3.OperationalError as e:
         raise OperationalError(e)
     return stored_events
示例#16
0
 def select_events(
     self,
     originator_id: UUID,
     gt: Optional[int] = None,
     lte: Optional[int] = None,
     desc: bool = False,
     limit: Optional[int] = None,
 ) -> List[StoredEvent]:
     statement = self.select_events_statement
     params: List[Any] = [originator_id.hex]
     if gt is not None:
         statement += "AND originator_version>? "
         params.append(gt)
     if lte is not None:
         statement += "AND originator_version<=? "
         params.append(lte)
     statement += "ORDER BY originator_version "
     if desc is False:
         statement += "ASC "
     else:
         statement += "DESC "
     if limit is not None:
         statement += "LIMIT ? "
         params.append(limit)
     stored_events = []
     with self.datastore.transaction(commit=False) as c:
         c.execute(statement, params)
         for row in c.fetchall():
             stored_events.append(
                 StoredEvent(
                     originator_id=UUID(row["originator_id"]),
                     originator_version=row["originator_version"],
                     topic=row["topic"],
                     state=row["state"],
                 ))
         pass  # for Coverage 5.5 bug with CPython 3.10.0rc1
     return stored_events
示例#17
0
    def test_retry_max_tracking_id_after_closing_connection(self):
        # Construct the recorder.
        recorder = self.create_recorder()

        # Check we have a connection (from create_table).
        self.assertTrue(self.datastore._connections)

        # Write a stored event.
        originator_id = uuid4()
        stored_event1 = StoredEvent(
            originator_id=originator_id,
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        recorder.insert_events([stored_event1],
                               tracking=Tracking("upstream", 1))

        # Close connections.
        pg_close_all_connections()

        # Select events.
        notification_id = recorder.max_tracking_id("upstream")
        self.assertEqual(notification_id, 1)
    def test_select(self):
        recorder = SQLiteApplicationRecorder(SQLiteDatastore(":memory:"))
        recorder.create_table()

        # Construct notification log.
        notification_log = LocalNotificationLog(recorder, section_size=10)

        # Select start 1, limit 10
        notifications = notification_log.select(1, 10)
        self.assertEqual(len(notifications), 0)

        # Write 5 events.
        originator_id = uuid4()
        for i in range(5):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events([stored_event])

        # Select start 1, limit 10
        notifications = notification_log.select(1, 5)
        self.assertEqual(len(notifications), 5)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[1].id, 2)
        self.assertEqual(notifications[2].id, 3)
        self.assertEqual(notifications[3].id, 4)
        self.assertEqual(notifications[4].id, 5)

        # Select start 1, limit 10
        notifications = notification_log.select(1, 5)
        self.assertEqual(len(notifications), 5)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[1].id, 2)
        self.assertEqual(notifications[2].id, 3)
        self.assertEqual(notifications[3].id, 4)
        self.assertEqual(notifications[4].id, 5)

        # Select start 6, limit 5
        notifications = notification_log.select(6, 5)
        self.assertEqual(len(notifications), 0)

        # Write 4 events.
        originator_id = uuid4()
        for i in range(4):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events([stored_event])

        # Select start 6, limit 5
        notifications = notification_log.select(6, 5)
        self.assertEqual(len(notifications), 4)  # event notifications
        self.assertEqual(notifications[0].id, 6)
        self.assertEqual(notifications[1].id, 7)
        self.assertEqual(notifications[2].id, 8)
        self.assertEqual(notifications[3].id, 9)

        # Select start 3, limit 5
        notifications = notification_log.select(3, 5)
        self.assertEqual(len(notifications), 5)  # event notifications
        self.assertEqual(notifications[0].id, 3)
        self.assertEqual(notifications[1].id, 4)
        self.assertEqual(notifications[2].id, 5)
        self.assertEqual(notifications[3].id, 6)
        self.assertEqual(notifications[4].id, 7)

        # Notification log limits limit.
        # Select start 1, limit 20
        with self.assertRaises(ValueError) as cm:
            notification_log.select(1, 20)
        self.assertEqual(
            cm.exception.args[0], "Requested limit 20 greater than section size 10"
        )
    def test_insert_select(self):
        # Construct the recorder.
        recorder = self.create_recorder()

        # Get current position.
        self.assertEqual(
            recorder.max_tracking_id("upstream_app"),
            0,
        )

        # Write two stored events.
        originator_id1 = uuid4()
        originator_id2 = uuid4()

        stored_event1 = StoredEvent(
            originator_id=originator_id1,
            originator_version=1,
            topic="topic1",
            state=b"state1",
        )
        stored_event2 = StoredEvent(
            originator_id=originator_id1,
            originator_version=2,
            topic="topic2",
            state=b"state2",
        )
        stored_event3 = StoredEvent(
            originator_id=originator_id2,
            originator_version=1,
            topic="topic3",
            state=b"state3",
        )
        stored_event4 = StoredEvent(
            originator_id=originator_id2,
            originator_version=2,
            topic="topic4",
            state=b"state4",
        )
        tracking1 = Tracking(
            application_name="upstream_app",
            notification_id=1,
        )
        tracking2 = Tracking(
            application_name="upstream_app",
            notification_id=2,
        )

        # Insert two events with tracking info.
        recorder.insert_events(
            stored_events=[
                stored_event1,
                stored_event2,
            ],
            tracking=tracking1,
        )

        # Get current position.
        self.assertEqual(
            recorder.max_tracking_id("upstream_app"),
            1,
        )

        # Check can't insert third event with same tracking info.
        with self.assertRaises(IntegrityError):
            recorder.insert_events(
                stored_events=[stored_event3],
                tracking=tracking1,
            )

        # Get current position.
        self.assertEqual(
            recorder.max_tracking_id("upstream_app"),
            1,
        )

        # Insert third event with different tracking info.
        recorder.insert_events(
            stored_events=[stored_event3],
            tracking=tracking2,
        )

        # Get current position.
        self.assertEqual(
            recorder.max_tracking_id("upstream_app"),
            2,
        )

        # Insert fourth event without tracking info.
        recorder.insert_events(stored_events=[stored_event4], )

        # Get current position.
        self.assertEqual(
            recorder.max_tracking_id("upstream_app"),
            2,
        )
示例#20
0
    def test_insert_and_select(self) -> None:

        # Construct the recorder.
        recorder = self.create_recorder()

        # Check we can call insert_events() with an empty list.
        notification_ids = recorder.insert_events([])
        self.assertEqual(notification_ids, None)

        # Select stored events, expect empty list.
        originator_id1 = uuid4()
        self.assertEqual(
            recorder.select_events(originator_id1, desc=True, limit=1),
            [],
        )

        # Write a stored event.
        stored_event1 = StoredEvent(
            originator_id=originator_id1,
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        notification_ids = recorder.insert_events([stored_event1])
        self.assertEqual(notification_ids, None)

        # Select stored events, expect list of one.
        stored_events = recorder.select_events(originator_id1)
        self.assertEqual(len(stored_events), 1)
        assert stored_events[0].originator_id == originator_id1
        assert stored_events[0].originator_version == 0
        assert stored_events[0].topic == "topic1"

        # Check get record conflict error if attempt to store it again.
        stored_events = recorder.select_events(originator_id1)
        with self.assertRaises(IntegrityError):
            recorder.insert_events([stored_event1])

        # Check writing of events is atomic.
        stored_event2 = StoredEvent(
            originator_id=originator_id1,
            originator_version=1,
            topic="topic2",
            state=b"state2",
        )
        with self.assertRaises(IntegrityError):
            recorder.insert_events([stored_event1, stored_event2])

        with self.assertRaises(IntegrityError):
            recorder.insert_events([stored_event2, stored_event2])

        # Check still only have one record.
        stored_events = recorder.select_events(originator_id1)
        self.assertEqual(len(stored_events), 1)
        assert stored_events[0].originator_id == originator_id1
        assert stored_events[0].originator_version == 0
        assert stored_events[0].topic == "topic1"

        # Check can write two events together.
        stored_event3 = StoredEvent(
            originator_id=originator_id1,
            originator_version=2,
            topic="topic3",
            state=b"state3",
        )
        notification_ids = recorder.insert_events(
            [stored_event2, stored_event3])
        self.assertEqual(notification_ids, None)

        # Check we got what was written.
        stored_events = recorder.select_events(originator_id1)
        self.assertEqual(len(stored_events), 3)
        assert stored_events[0].originator_id == originator_id1
        assert stored_events[0].originator_version == 0
        assert stored_events[0].topic == "topic1"
        self.assertEqual(stored_events[0].state, b"state1")
        assert stored_events[1].originator_id == originator_id1
        assert stored_events[1].originator_version == 1
        assert stored_events[1].topic == "topic2"
        assert stored_events[1].state == b"state2"
        assert stored_events[2].originator_id == originator_id1
        assert stored_events[2].originator_version == 2
        assert stored_events[2].topic == "topic3"
        assert stored_events[2].state == b"state3"

        # Check we can get the last one recorded (used to get last snapshot).
        events = recorder.select_events(originator_id1, desc=True, limit=1)
        self.assertEqual(len(events), 1)
        self.assertEqual(
            events[0],
            stored_event3,
        )

        # Check we can get the last one before a particular version.
        events = recorder.select_events(originator_id1,
                                        lte=1,
                                        desc=True,
                                        limit=1)
        self.assertEqual(len(events), 1)
        self.assertEqual(
            events[0],
            stored_event2,
        )

        # Check we can get events between particular versions.
        events = recorder.select_events(originator_id1, gt=0, lte=2)
        self.assertEqual(len(events), 2)
        self.assertEqual(
            events[0],
            stored_event2,
        )
        self.assertEqual(
            events[1],
            stored_event3,
        )

        # Check aggregate sequences are distinguished.
        originator_id2 = uuid4()
        self.assertEqual(
            recorder.select_events(originator_id2),
            [],
        )

        # Write a stored event.
        stored_event4 = StoredEvent(
            originator_id=originator_id2,
            originator_version=0,
            topic="topic4",
            state=b"state4",
        )
        recorder.insert_events([stored_event4])
        self.assertEqual(
            recorder.select_events(originator_id2),
            [stored_event4],
        )
示例#21
0
    def test_insert_select(self) -> None:
        # Construct the recorder.
        recorder = self.create_recorder()

        # Check notifications methods work when there aren't any.
        self.assertEqual(
            recorder.max_notification_id(),
            0,
        )
        self.assertEqual(
            len(recorder.select_notifications(1, 3)),
            0,
        )
        self.assertEqual(
            len(recorder.select_notifications(1, 3, topics=["topic1"])),
            0,
        )

        # Write two stored events.
        originator_id1 = uuid4()
        originator_id2 = uuid4()

        stored_event1 = StoredEvent(
            originator_id=originator_id1,
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        stored_event2 = StoredEvent(
            originator_id=originator_id1,
            originator_version=1,
            topic="topic2",
            state=b"state2",
        )
        stored_event3 = StoredEvent(
            originator_id=originator_id2,
            originator_version=1,
            topic="topic3",
            state=b"state3",
        )

        notification_ids = recorder.insert_events([])
        self.assertEqual(notification_ids, [])

        notification_ids = recorder.insert_events(
            [stored_event1, stored_event2])
        self.assertEqual(notification_ids, [1, 2])

        notification_ids = recorder.insert_events([stored_event3])
        self.assertEqual(notification_ids, [3])

        stored_events1 = recorder.select_events(originator_id1)
        stored_events2 = recorder.select_events(originator_id2)

        # Check we got what was written.
        self.assertEqual(len(stored_events1), 2)
        self.assertEqual(len(stored_events2), 1)

        notifications = recorder.select_notifications(1, 3)
        self.assertEqual(len(notifications), 3)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic1")
        self.assertEqual(notifications[0].state, b"state1")
        self.assertEqual(notifications[1].id, 2)
        self.assertEqual(notifications[1].originator_id, originator_id1)
        self.assertEqual(notifications[1].topic, "topic2")
        self.assertEqual(notifications[1].state, b"state2")
        self.assertEqual(notifications[2].id, 3)
        self.assertEqual(notifications[2].originator_id, originator_id2)
        self.assertEqual(notifications[2].topic, "topic3")
        self.assertEqual(notifications[2].state, b"state3")

        notifications = recorder.select_notifications(
            1, 3, topics=["topic1", "topic2", "topic3"])
        self.assertEqual(len(notifications), 3)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic1")
        self.assertEqual(notifications[0].state, b"state1")
        self.assertEqual(notifications[1].id, 2)
        self.assertEqual(notifications[1].originator_id, originator_id1)
        self.assertEqual(notifications[1].topic, "topic2")
        self.assertEqual(notifications[1].state, b"state2")
        self.assertEqual(notifications[2].id, 3)
        self.assertEqual(notifications[2].originator_id, originator_id2)
        self.assertEqual(notifications[2].topic, "topic3")
        self.assertEqual(notifications[2].state, b"state3")

        notifications = recorder.select_notifications(1, 3, topics=["topic1"])
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic1")
        self.assertEqual(notifications[0].state, b"state1")

        notifications = recorder.select_notifications(1, 3, topics=["topic2"])
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 2)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic2")
        self.assertEqual(notifications[0].state, b"state2")

        notifications = recorder.select_notifications(1, 3, topics=["topic3"])
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 3)
        self.assertEqual(notifications[0].originator_id, originator_id2)
        self.assertEqual(notifications[0].topic, "topic3")
        self.assertEqual(notifications[0].state, b"state3")

        notifications = recorder.select_notifications(
            1, 3, topics=["topic1", "topic3"])
        self.assertEqual(len(notifications), 2)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic1")
        self.assertEqual(notifications[0].state, b"state1")
        self.assertEqual(notifications[1].id, 3)
        self.assertEqual(notifications[1].topic, "topic3")
        self.assertEqual(notifications[1].state, b"state3")

        self.assertEqual(
            recorder.max_notification_id(),
            3,
        )

        notifications = recorder.select_notifications(1, 1)
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 1)

        notifications = recorder.select_notifications(2, 1)
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 2)

        notifications = recorder.select_notifications(2, 2)
        self.assertEqual(len(notifications), 2)
        self.assertEqual(notifications[0].id, 2)
        self.assertEqual(notifications[1].id, 3)

        notifications = recorder.select_notifications(3, 1)
        self.assertEqual(len(notifications), 1, len(notifications))
        self.assertEqual(notifications[0].id, 3)

        notifications = recorder.select_notifications(start=2,
                                                      limit=10,
                                                      stop=2)
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 2)

        notifications = recorder.select_notifications(start=1,
                                                      limit=10,
                                                      stop=2)
        self.assertEqual(len(notifications), 2, len(notifications))
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[1].id, 2)
    def test(self):
        recorder = SQLiteApplicationRecorder(SQLiteDatastore(":memory:"))
        recorder.create_table()

        # Construct notification log.
        notification_log = LocalNotificationLog(recorder, section_size=5)

        # Get the "current" section of log.
        section = notification_log["1,10"]
        self.assertEqual(len(section.items), 0)  # event notifications
        self.assertEqual(section.id, None)
        self.assertEqual(section.next_id, None)

        # Write 5 events.
        originator_id = uuid4()
        for i in range(5):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events([stored_event])

        # Get the "head" section of log.
        section = notification_log["1,10"]
        self.assertEqual(len(section.items), 5)  # event notifications
        self.assertEqual(section.items[0].id, 1)
        self.assertEqual(section.items[1].id, 2)
        self.assertEqual(section.items[2].id, 3)
        self.assertEqual(section.items[3].id, 4)
        self.assertEqual(section.items[4].id, 5)
        self.assertEqual(section.id, "1,5")
        self.assertEqual(section.next_id, "6,10")

        # Get the "1,5" section of log.
        section = notification_log["1,5"]
        self.assertEqual(len(section.items), 5)  # event notifications
        self.assertEqual(section.items[0].id, 1)
        self.assertEqual(section.items[1].id, 2)
        self.assertEqual(section.items[2].id, 3)
        self.assertEqual(section.items[3].id, 4)
        self.assertEqual(section.items[4].id, 5)
        self.assertEqual(section.id, "1,5")
        self.assertEqual(section.next_id, "6,10")

        # Get the next section of log.
        section = notification_log["6,10"]
        self.assertEqual(len(section.items), 0)  # event notifications
        self.assertEqual(section.id, None)
        self.assertEqual(section.next_id, None)

        # Write 4 events.
        originator_id = uuid4()
        for i in range(4):
            stored_event = StoredEvent(
                originator_id=originator_id,
                originator_version=i,
                topic="topic",
                state=b"state",
            )
            recorder.insert_events([stored_event])

        # Get the next section of log.
        section = notification_log["6,10"]
        self.assertEqual(len(section.items), 4)  # event notifications
        self.assertEqual(section.items[0].id, 6)
        self.assertEqual(section.items[1].id, 7)
        self.assertEqual(section.items[2].id, 8)
        self.assertEqual(section.items[3].id, 9)
        self.assertEqual(section.id, "6,9")
        self.assertEqual(section.next_id, None)

        # Start at non-regular section start.
        section = notification_log["3,7"]
        self.assertEqual(len(section.items), 5)  # event notifications
        self.assertEqual(section.items[0].id, 3)
        self.assertEqual(section.items[1].id, 4)
        self.assertEqual(section.items[2].id, 5)
        self.assertEqual(section.items[3].id, 6)
        self.assertEqual(section.items[4].id, 7)
        self.assertEqual(section.id, "3,7")
        self.assertEqual(section.next_id, "8,12")

        # Notification log limits section size.
        section = notification_log["3,10"]
        self.assertEqual(len(section.items), 5)  # event notifications
        self.assertEqual(section.items[0].id, 3)
        self.assertEqual(section.items[1].id, 4)
        self.assertEqual(section.items[2].id, 5)
        self.assertEqual(section.items[3].id, 6)
        self.assertEqual(section.items[4].id, 7)
        self.assertEqual(section.id, "3,7")
        self.assertEqual(section.next_id, "8,12")

        # Reader limits section size.
        section = notification_log["3,4"]
        self.assertEqual(len(section.items), 2)  # event notifications
        self.assertEqual(section.items[0].id, 3)
        self.assertEqual(section.items[1].id, 4)
        self.assertEqual(section.id, "3,4")
        self.assertEqual(section.next_id, "5,6")

        # Meaningless section ID.
        section = notification_log["3,2"]
        self.assertEqual(len(section.items), 0)  # event notifications
        self.assertEqual(section.id, None)
        self.assertEqual(section.next_id, None)
    def test_insert_select(self):
        # Construct the recorder.
        recorder = self.create_recorder()

        self.assertEqual(
            recorder.max_notification_id(),
            0,
        )

        # Write two stored events.
        originator_id1 = uuid4()
        originator_id2 = uuid4()

        stored_event1 = StoredEvent(
            originator_id=originator_id1,
            originator_version=0,
            topic="topic1",
            state=b"state1",
        )
        stored_event2 = StoredEvent(
            originator_id=originator_id1,
            originator_version=1,
            topic="topic2",
            state=b"state2",
        )
        stored_event3 = StoredEvent(
            originator_id=originator_id2,
            originator_version=1,
            topic="topic3",
            state=b"state3",
        )

        recorder.insert_events([stored_event1, stored_event2])
        recorder.insert_events([stored_event3])

        stored_events1 = recorder.select_events(originator_id1)
        stored_events2 = recorder.select_events(originator_id2)

        # Check we got what was written.
        self.assertEqual(len(stored_events1), 2)
        self.assertEqual(len(stored_events2), 1)

        notifications = recorder.select_notifications(1, 3)
        self.assertEqual(len(notifications), 3)
        self.assertEqual(notifications[0].id, 1)
        self.assertEqual(notifications[0].originator_id, originator_id1)
        self.assertEqual(notifications[0].topic, "topic1")
        self.assertEqual(notifications[0].state, b"state1")
        self.assertEqual(notifications[1].id, 2)
        self.assertEqual(notifications[1].topic, "topic2")
        self.assertEqual(notifications[1].state, b"state2")
        self.assertEqual(notifications[2].id, 3)
        self.assertEqual(notifications[2].topic, "topic3")
        self.assertEqual(notifications[2].state, b"state3")

        self.assertEqual(
            recorder.max_notification_id(),
            3,
        )

        notifications = recorder.select_notifications(1, 1)
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 1)

        notifications = recorder.select_notifications(2, 1)
        self.assertEqual(len(notifications), 1)
        self.assertEqual(notifications[0].id, 2)

        notifications = recorder.select_notifications(2, 2)
        self.assertEqual(len(notifications), 2)
        self.assertEqual(notifications[0].id, 2)
        self.assertEqual(notifications[1].id, 3)

        notifications = recorder.select_notifications(3, 1)
        self.assertEqual(len(notifications), 1, len(notifications))
        self.assertEqual(notifications[0].id, 3)