Index: src/wp-includes/class-wp-comment-query.php
===================================================================
--- src/wp-includes/class-wp-comment-query.php	(revision 45022)
+++ src/wp-includes/class-wp-comment-query.php	(working copy)
@@ -379,32 +379,50 @@
 			$this->meta_query_clauses = $this->meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $this );
 		}
 
-		/*
-		 * Only use the args defined in the query_var_defaults to compute the key,
-		 * but ignore 'fields', which does not affect query results.
+		/**
+		 * Filters the comments array before the query takes place.
+		 *
+		 * Return a non-null value to bypass WordPress's default comment queries.
+		 *
+		 * @since x.x.x
+		 *
+		 * @param array|int|null   $results Return null to allow WP to run its normal queries.
+		 *                                  Otherwise, return an array of comment data,
+		 *                                  an array of int if `fields=ids` or
+		 *                                  an int if `count=true` to short-circuit WP's comment query.
+		 * @param WP_Comment_Query  $this   The WP_Comment_Query instance (passed by reference).
 		 */
-		$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
-		unset( $_args['fields'] );
+		$this->comments = apply_filters_ref_array( 'comments_pre_query', array( null, &$this ) );
 
-		$key          = md5( serialize( $_args ) );
-		$last_changed = wp_cache_get_last_changed( 'comment' );
+		$comment_ids = array();
 
-		$cache_key   = "get_comments:$key:$last_changed";
-		$cache_value = wp_cache_get( $cache_key, 'comment' );
-		if ( false === $cache_value ) {
-			$comment_ids = $this->get_comment_ids();
-			if ( $comment_ids ) {
-				$this->set_found_comments();
+		if ( null === $this->comments ) {
+			// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
+			$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
+
+			// Ignore the $fields argument as the queried result will be the same regardless.
+			unset( $_args['fields'] );
+
+			$key          = md5( serialize( $_args ) );
+			$last_changed = wp_cache_get_last_changed( 'comment' );
+
+			$cache_key   = "get_comments:$key:$last_changed";
+			$cache_value = wp_cache_get( $cache_key, 'comment' );
+			if ( false === $cache_value ) {
+				$comment_ids = $this->get_comment_ids();
+				if ( $comment_ids ) {
+					$this->set_found_comments();
+				}
+
+				$cache_value = array(
+					'comment_ids'    => $comment_ids,
+					'found_comments' => $this->found_comments,
+				);
+				wp_cache_add( $cache_key, $cache_value, 'comment' );
+			} else {
+				$comment_ids          = $cache_value['comment_ids'];
+				$this->found_comments = $cache_value['found_comments'];
 			}
-
-			$cache_value = array(
-				'comment_ids'    => $comment_ids,
-				'found_comments' => $this->found_comments,
-			);
-			wp_cache_add( $cache_key, $cache_value, 'comment' );
-		} else {
-			$comment_ids          = $cache_value['comment_ids'];
-			$this->found_comments = $cache_value['found_comments'];
 		}
 
 		if ( $this->found_comments && $this->query_vars['number'] ) {
@@ -413,35 +431,45 @@
 
 		// If querying for a count only, there's nothing more to do.
 		if ( $this->query_vars['count'] ) {
-			// $comment_ids is actually a count in this case.
-			return intval( $comment_ids );
+			if ( null === $this->comments ) {
+				// $comment_ids is actually a count in this case.
+				return intval( $comment_ids );
+			}
+			// The comments_pre_query callback should be smart enough to test it and return the right result.
+			return intval( $this->comments );
 		}
 
 		$comment_ids = array_map( 'intval', $comment_ids );
 
 		if ( 'ids' == $this->query_vars['fields'] ) {
-			$this->comments = $comment_ids;
+			if ( null === $this->comments ) {
+				$this->comments = $comment_ids;
+			}
 			return $this->comments;
 		}
 
-		_prime_comment_caches( $comment_ids, $this->query_vars['update_comment_meta_cache'] );
+		if ( null === $this->comments ) {
+			_prime_comment_caches( $comment_ids, $this->query_vars['update_comment_meta_cache'] );
 
-		// Fetch full comment objects from the primed cache.
-		$_comments = array();
-		foreach ( $comment_ids as $comment_id ) {
-			if ( $_comment = get_comment( $comment_id ) ) {
-				$_comments[] = $_comment;
+			// Fetch full comment objects from the primed cache.
+			$_comments = array();
+			foreach ( $comment_ids as $comment_id ) {
+				if ( $_comment = get_comment( $comment_id ) ) {
+					$_comments[] = $_comment;
+				}
 			}
-		}
 
-		// Prime comment post caches.
-		if ( $this->query_vars['update_comment_post_cache'] ) {
-			$comment_post_ids = array();
-			foreach ( $_comments as $_comment ) {
-				$comment_post_ids[] = $_comment->comment_post_ID;
+			// Prime comment post caches.
+			if ( $this->query_vars['update_comment_post_cache'] ) {
+				$comment_post_ids = array();
+				foreach ( $_comments as $_comment ) {
+					$comment_post_ids[] = $_comment->comment_post_ID;
+				}
+
+				_prime_post_caches( $comment_post_ids, false, false );
 			}
-
-			_prime_post_caches( $comment_post_ids, false, false );
+		} else {
+			$_comments = $this->comments;
 		}
 
 		/**
@@ -457,8 +485,10 @@
 		// Convert to WP_Comment instances
 		$comments = array_map( 'get_comment', $_comments );
 
-		if ( $this->query_vars['hierarchical'] ) {
-			$comments = $this->fill_descendants( $comments );
+		if ( null === $this->comments ) {
+			if ( $this->query_vars['hierarchical'] ) {
+				$comments = $this->fill_descendants( $comments );
+			}
 		}
 
 		$this->comments = $comments;
Index: tests/phpunit/tests/comment/query.php
===================================================================
--- tests/phpunit/tests/comment/query.php	(revision 45022)
+++ tests/phpunit/tests/comment/query.php	(working copy)
@@ -4882,4 +4882,37 @@
 
 		$this->assertEqualSets( $c1, $found );
 	}
+
+	/**
+	 * @ticket 45800
+	 */
+	public function test_comments_pre_query_filter_should_bypass_database_query() {
+		global $wpdb;
+
+		add_filter( 'comments_pre_query', array( __CLASS__, 'filter_comments_pre_query' ), 10, 2 );
+
+		$num_queries = $wpdb->num_queries;
+		$q           = new WP_Comment_Query(
+			array(
+				'fields' => 'ids',
+			)
+		);
+
+		remove_filter( 'comments_pre_query', array( __CLASS__, 'filter_comments_pre_query' ), 10, 2 );
+
+		// Make sure no queries were executed.
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+
+		// We manually inserted a non-existing comment ID and overrode the results with it.
+		$this->assertSame( array( 555 ), $q->comments );
+
+		// Make sure manually setting found_comments doesn't get overwritten.
+		$this->assertEquals( 1, $q->found_comments );
+	}
+
+	public static function filter_comments_pre_query( $comments, $query ) {
+		$query->found_comments = 1;
+
+		return array( 555 );
+	}
 }
