dbCore = $dbCore; } /** * The (status, disabled) distribution of the rows a condition selects. * Take this immediately before a mutation and hand it to syncSince(). * * @param string $whereClause SQL WHERE body. Plugin-controlled text only: * every caller-supplied value must travel in $params. * @param array $params Bound parameters for $whereClause. * @return array|null Null when the * read failed, which makes the paired sync a no-op. */ public function snapshot(string $whereClause, array $params = array()): ?array { $redirectsTable = $this->dbCore->doTableNameReplacements("{wp_abj404_redirects}"); $query = "SELECT status, disabled, COUNT(*) as row_count FROM `" . $redirectsTable . "`" . " WHERE " . $whereClause . " GROUP BY status, disabled"; $options = array(); if ($params !== array()) { $options['query_params'] = $params; } $result = $this->dbCore->queryAndGetResults($query, $options); if (!empty($result['last_error']) || !empty($result['timed_out'])) { return null; } $histogram = array(); $rows = isset($result['rows']) && is_array($result['rows']) ? $result['rows'] : array(); foreach ($rows as $row) { if (!is_array($row)) { continue; } $histogram[] = array( 'status' => isset($row['status']) && is_scalar($row['status']) ? (int)$row['status'] : -1, 'disabled' => isset($row['disabled']) && is_scalar($row['disabled']) ? (int)$row['disabled'] : -1, 'count' => isset($row['row_count']) && is_scalar($row['row_count']) ? (int)$row['row_count'] : 0, ); } return $histogram; } /** * Apply the delta between a snapshot and the current state of the same * rows. A no-op when either read failed, so a mutation never publishes a * count it could not derive. * * @param array|null $before From snapshot(). * @param string $whereClause Same condition the snapshot used. * @param array $params Same parameters the snapshot used. * @return void */ public function syncSince(?array $before, string $whereClause, array $params = array()): void { if ($before === null) { return; } $after = $this->snapshot($whereClause, $params); if ($after === null) { return; } ABJ_404_Solution_StatusCountsRepository::applyDelta( ABJ_404_Solution_StatusCountBuckets::delta($before, $after) ); } /** * Apply the delta for rows that are simply gone, for a mutation whose * after-state needs no second read (an unconditional DELETE of exactly * the snapshotted set). * * @param array|null $removed From snapshot(). * @return void */ public function syncRemoved(?array $removed): void { if ($removed === null || $removed === array()) { return; } ABJ_404_Solution_StatusCountsRepository::applyDelta( ABJ_404_Solution_StatusCountBuckets::delta($removed, array()) ); } /** * Apply the delta for one freshly inserted row. An insert needs no reads * at all: the row that just landed is the whole delta, which matters on * the frontend capture hot path where an extra query per 404 is not free. * * @param int $status * @param int $disabled * @return void */ public function syncInserted(int $status, int $disabled): void { ABJ_404_Solution_StatusCountsRepository::applyDelta( ABJ_404_Solution_StatusCountBuckets::deltaForRows($status, $disabled, 1, 1) ); } }