dbFactory->getEchoDb( DB_PRIMARY ); $row = $event->toDbArray(); $dbw->newInsertQueryBuilder() ->insertInto( 'echo_event' ) ->row( $row ) ->caller( __METHOD__ ) ->execute(); $id = $dbw->insertId(); $listeners = $this->getMethodListeners( __FUNCTION__ ); foreach ( $listeners as $listener ) { $dbw->onTransactionCommitOrIdle( $listener, __METHOD__ ); } return $id; } /** * Create an Event by id * * @param int $id * @param bool $fromPrimary * @return Event|false False if it wouldn't load/unserialize */ public function fetchById( $id, $fromPrimary = false ) { $db = $fromPrimary ? $this->dbFactory->getEchoDb( DB_PRIMARY ) : $this->dbFactory->getEchoDb( DB_REPLICA ); $row = $db->selectRow( 'echo_event', Event::selectFields(), [ 'event_id' => $id ], __METHOD__ ); // If the row was not found, fall back on the primary database if it makes sense to do so if ( !$row && !$fromPrimary && $this->dbFactory->canRetryPrimary() ) { return $this->fetchById( $id, true ); } elseif ( !$row ) { throw new InvalidArgumentException( "No Event found with ID: $id" ); } return Event::newFromRow( $row ); } /** * @param int[] $eventIds * @param bool $deleted */ public function toggleDeleted( array $eventIds, $deleted ) { $dbw = $this->dbFactory->getEchoDb( DB_PRIMARY ); $selectDeleted = $deleted ? 0 : 1; $setDeleted = $deleted ? 1 : 0; $dbw->newUpdateQueryBuilder() ->update( 'echo_event' ) ->set( [ 'event_deleted' => $setDeleted, ] ) ->where( [ 'event_deleted' => $selectDeleted, 'event_id' => $eventIds, ] ) ->caller( __METHOD__ ) ->execute(); } /** * Fetch events associated with a page * * @param int $pageId * @return Event[] Events */ public function fetchByPage( $pageId ) { $events = []; $seenEventIds = []; $dbr = $this->dbFactory->getEchoDb( DB_REPLICA ); // From echo_event $res = $dbr->select( [ 'echo_event' ], Event::selectFields(), [ 'event_page_id' => $pageId ], __METHOD__ ); foreach ( $res as $row ) { $event = Event::newFromRow( $row ); $events[] = $event; $seenEventIds[] = $event->getId(); } // From echo_target_page $conds = [ 'etp_page' => $pageId ]; if ( $seenEventIds ) { // Some events have both a title and target page(s). // Skip the events that were already found in the echo_event table (the query above). $conds[] = 'event_id NOT IN ( ' . $dbr->makeList( $seenEventIds ) . ' )'; } $res = $dbr->select( [ 'echo_event', 'echo_target_page' ], Event::selectFields(), $conds, __METHOD__, [ 'DISTINCT' ], [ 'echo_target_page' => [ 'INNER JOIN', 'event_id=etp_event' ] ] ); foreach ( $res as $row ) { $events[] = Event::newFromRow( $row ); } return $events; } /** * Fetch event IDs associated with a page * * @param int $pageId * @return int[] Event IDs */ public function fetchIdsByPage( $pageId ) { $events = $this->fetchByPage( $pageId ); $eventIds = array_map( static function ( Event $event ) { return $event->getId(); }, $events ); return $eventIds; } /** * Fetch events unread by a user and associated with a page * * @param User $user * @param int $pageId * @return Event[] */ public function fetchUnreadByUserAndPage( User $user, $pageId ) { $dbr = $this->dbFactory->getEchoDb( DB_REPLICA ); $fields = array_merge( Event::selectFields(), [ 'notification_timestamp' ] ); $res = $dbr->select( [ 'echo_event', 'echo_notification', 'echo_target_page' ], $fields, [ 'event_deleted' => 0, 'notification_user' => $user->getId(), 'notification_read_timestamp' => null, 'etp_page' => $pageId, ], __METHOD__, [], [ 'echo_target_page' => [ 'INNER JOIN', 'etp_event=event_id' ], 'echo_notification' => [ 'INNER JOIN', [ 'notification_event=event_id' ] ], ] ); $data = []; foreach ( $res as $row ) { $data[] = Event::newFromRow( $row ); } return $data; } /** * Find out which of the given event IDs are orphaned, and delete them. * * An event is orphaned if it is not referred to by any rows in the echo_notification or * echo_email_batch tables. If $ignoreUserId is set, rows for that user are not considered when * determining orphanhood; if $ignoreUserTable is set, this only applies to that table. * Use this when you've just recently deleted rows related to this user on the primary database, so that * this function won't refuse to delete recently-orphaned events because it still sees the * recently-deleted rows on the replica. * * @param array $eventIds Event IDs to check to see if they have become orphaned * @param int|null $ignoreUserId Allow events to be deleted if the only referring rows * have this user ID * @param string|null $ignoreUserTable Restrict $ignoreUserId to this table only * ('echo_notification' or 'echo_email_batch') */ public function deleteOrphanedEvents( array $eventIds, $ignoreUserId = null, $ignoreUserTable = null ) { $dbw = $this->dbFactory->getEchoDb( DB_PRIMARY ); $dbr = $this->dbFactory->getEchoDb( DB_REPLICA ); $notifJoinConds = []; $emailJoinConds = []; if ( $ignoreUserId !== null ) { if ( $ignoreUserTable === null || $ignoreUserTable === 'echo_notification' ) { $notifJoinConds[] = 'notification_user != ' . $dbr->addQuotes( $ignoreUserId ); } if ( $ignoreUserTable === null || $ignoreUserTable === 'echo_email_batch' ) { $emailJoinConds[] = 'eeb_user_id != ' . $dbr->addQuotes( $ignoreUserId ); } } $orphanedEventIds = $dbr->selectFieldValues( [ 'echo_event', 'echo_notification', 'echo_email_batch' ], 'event_id', [ 'event_id' => $eventIds, 'notification_timestamp' => null, 'eeb_user_id' => null ], __METHOD__, [], [ 'echo_notification' => [ 'LEFT JOIN', array_merge( [ 'notification_event=event_id' ], $notifJoinConds ) ], 'echo_email_batch' => [ 'LEFT JOIN', array_merge( [ 'eeb_event_id=event_id' ], $emailJoinConds ) ] ] ); if ( $orphanedEventIds ) { $dbw->newDeleteQueryBuilder() ->deleteFrom( 'echo_event' ) ->where( [ 'event_id' => $orphanedEventIds ] ) ->caller( __METHOD__ ) ->execute(); $dbw->newDeleteQueryBuilder() ->deleteFrom( 'echo_target_page' ) ->where( [ 'etp_event' => $orphanedEventIds ] ) ->caller( __METHOD__ ) ->execute(); } } }