%s AND SUBSTR(candidates.host, -%d) <> %s'; public function __construct( private QueryRunner $query_runner, private Tables $tables, private LinkRowMapper $link_row_mapper ) { } /** * @param list $skipped_hosts * @return list */ public function one_per_host( int $now, int $limit, array $skipped_hosts ): array { $skipped_host_conditions = str_repeat( self::SKIPPED_HOST_CONDITION, count( $skipped_hosts ) ); $query = 'SELECT ' . $this->tables->link_columns() . ' FROM ' . $this->tables->links() . ' links INNER JOIN ( SELECT MIN(candidates.id) AS first_id, MIN(candidates.check_after) AS due_at FROM ' . $this->tables->links() . ' candidates WHERE candidates.outcome = %s AND candidates.check_after <= %d' . $skipped_host_conditions . ' GROUP BY candidates.host ORDER BY due_at, first_id LIMIT %d ) due ON due.first_id = links.id ORDER BY due.due_at, due.first_id'; $values = array_merge( array( Outcome::Listed->value, $now ), $this->skipped_host_values( $skipped_hosts ), array( $limit ) ); $rows = $this->query_runner->rows( $this->query_runner->prepare( $query, ...$values ) ); $links = array(); foreach ( $rows as $row ) { $links[] = $this->link_row_mapper->to_link( $row ); } return $links; } /** * @param list $skipped_hosts * @return list */ private function skipped_host_values( array $skipped_hosts ): array { $values = array(); foreach ( $skipped_hosts as $skipped_host ) { $host = strtolower( $skipped_host ); $suffix = self::SUBDOMAIN_SEPARATOR . $host; array_push( $values, $host, strlen( $suffix ), $suffix ); } return $values; } }