LUCENE-10469: Fix score mode propagation in ConstantScoreQuery. (#750)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 2c4ec5f..8283442 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -270,6 +270,8 @@
   calculation, as this could cause problems with wrapper queries like BooleanQuery which
   expect their child queries hashcodes to be stable. (Alan Woodward)
 
+* LUCENE-10469: Fix ScoreMode propagation by ConstantScoreQuery. (Adrien Grand)
+
 Other
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
index fec12c6..2f75a56 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
@@ -114,7 +114,16 @@
   @Override
   public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
       throws IOException {
-    final Weight innerWeight = searcher.createWeight(query, ScoreMode.COMPLETE_NO_SCORES, 1f);
+    // If the score mode is exhaustive then pass COMPLETE_NO_SCORES, otherwise pass TOP_DOCS to make
+    // sure to not disable any of the dynamic pruning optimizations for queries sorted by field or
+    // top scores.
+    final ScoreMode innerScoreMode;
+    if (scoreMode.isExhaustive()) {
+      innerScoreMode = ScoreMode.COMPLETE_NO_SCORES;
+    } else {
+      innerScoreMode = ScoreMode.TOP_DOCS;
+    }
+    final Weight innerWeight = searcher.createWeight(query, innerScoreMode, 1f);
     if (scoreMode.needsScores()) {
       return new ConstantScoreWeight(this, boost) {
         @Override
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestNeedsScores.java b/lucene/core/src/test/org/apache/lucene/search/TestNeedsScores.java
index 57ecad5..c4c6b06 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestNeedsScores.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestNeedsScores.java
@@ -70,9 +70,31 @@
   /** nested inside constant score query */
   public void testConstantScoreQuery() throws Exception {
     Query term = new TermQuery(new Term("field", "this"));
+
+    // Counting queries and top-score queries that compute the hit count should use
+    // COMPLETE_NO_SCORES
     Query constantScore =
         new ConstantScoreQuery(new AssertNeedsScores(term, ScoreMode.COMPLETE_NO_SCORES));
+    assertEquals(5, searcher.count(constantScore));
+
+    TopDocs hits =
+        searcher.search(
+            constantScore, TopScoreDocCollector.createSharedManager(5, null, Integer.MAX_VALUE));
+    assertEquals(5, hits.totalHits.value);
+
+    // Queries that support dynamic pruning like top-score or top-doc queries that do not compute
+    // the hit count should use TOP_DOCS
+    constantScore = new ConstantScoreQuery(new AssertNeedsScores(term, ScoreMode.TOP_DOCS));
     assertEquals(5, searcher.search(constantScore, 5).totalHits.value);
+
+    assertEquals(
+        5, searcher.search(constantScore, 5, new Sort(SortField.FIELD_DOC)).totalHits.value);
+
+    assertEquals(
+        5,
+        searcher.search(constantScore, 5, new Sort(SortField.FIELD_DOC, SortField.FIELD_SCORE))
+            .totalHits
+            .value);
   }
 
   /** when not sorting by score */