Index: lucene/src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/BooleanQuery.java	(revision 1148635)
+++ lucene/src/java/org/apache/lucene/search/BooleanQuery.java	(working copy)
@@ -33,6 +33,7 @@
 public class BooleanQuery extends Query implements Iterable<BooleanClause> {
 
   private static int maxClauseCount = 1024;
+  private boolean rewriteToConjunctionTermsQuery = true;
 
   /** Thrown when an attempt is made to add more than {@link
    * #getMaxClauseCount()} clauses. This typically happens if
@@ -135,7 +136,8 @@
   public void add(BooleanClause clause) {
     if (clauses.size() >= maxClauseCount)
       throw new TooManyClauses();
-
+    rewriteToConjunctionTermsQuery = rewriteToConjunctionTermsQuery
+        && clause.isRequired() && clause.getQuery() instanceof TermQuery;
     clauses.add(clause);
   }
 
@@ -357,6 +359,15 @@
 
   @Override
   public Query rewrite(IndexReader reader) throws IOException {
+    if (rewriteToConjunctionTermsQuery && !clauses.isEmpty() && minNrShouldMatch == 0) {
+      TermQuery[] terms = new TermQuery[clauses.size()];
+      int i = 0;
+      for (BooleanClause clause : clauses) {
+        assert clause.isRequired() && clause.getQuery() instanceof TermQuery;
+        terms[i++]  = (TermQuery) clause.getQuery();
+      }
+      return new ConjunctionTermQuery(disableCoord, terms);
+    }
     if (minNrShouldMatch == 0 && clauses.size() == 1) {                    // optimize 1-clause queries
       BooleanClause c = clauses.get(0);
       if (!c.isProhibited()) {			  // just return clause
Index: lucene/src/java/org/apache/lucene/search/ConjunctionTermQuery.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/ConjunctionTermQuery.java	(revision 0)
+++ lucene/src/java/org/apache/lucene/search/ConjunctionTermQuery.java	(revision 0)
@@ -0,0 +1,219 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.index.IndexReader.AtomicReaderContext;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermState;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.ConjunctionTermScorer.DocsAndFreqs;
+import org.apache.lucene.search.Similarity.ExactDocScorer;
+import org.apache.lucene.search.TermQuery.TermWeight;
+import org.apache.lucene.util.ToStringUtils;
+
+/**
+ * Specialized Boolean Conjunction Query used if all query clauses are required
+ * {@link TermQuery} instances.
+ * 
+ * @lucene.internal
+ */
+final class ConjunctionTermQuery extends Query {
+  private final TermQuery[] terms;
+  private final boolean disableCoord;
+
+  ConjunctionTermQuery(boolean disableCoord, TermQuery... terms) {
+    this.terms = terms;
+    this.disableCoord = disableCoord;
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder buffer = new StringBuilder();
+    boolean needParens = (getBoost() != 1.0);
+    if (needParens) {
+      buffer.append("(");
+    }
+    for (int i = 0; i < terms.length; i++) {
+      buffer.append("+");
+
+      Query subQuery = terms[i];
+      if (subQuery != null) {
+        buffer.append(subQuery.toString(field));
+      } else {
+        buffer.append("null");
+      }
+      if (i != terms.length - 1)
+        buffer.append(" ");
+    }
+    if (needParens) {
+      buffer.append(")");
+    }
+    if (getBoost() != 1.0f) {
+      buffer.append(ToStringUtils.boost(getBoost()));
+    }
+
+    return buffer.toString();
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher) throws IOException {
+    return new ConjunctionTermWeight(searcher, disableCoord, terms);
+  }
+
+  final class ConjunctionTermWeight extends Weight {
+    private final TermWeight[] termWeights;
+    private final SimilarityProvider similarityProvider;
+
+    public ConjunctionTermWeight(IndexSearcher searcher, boolean disableCoord,
+        TermQuery[] terms) throws IOException {
+      termWeights = new TermWeight[terms.length];
+      this.similarityProvider = searcher.getSimilarityProvider();
+
+      for (int i = 0; i < terms.length; i++) {
+        termWeights[i] = (TermWeight) terms[i].createWeight(searcher);
+      }
+    }
+
+    public float coord(int overlap, int maxOverlap) {
+      return similarityProvider.coord(overlap, maxOverlap);
+    }
+
+    @Override
+    public Explanation explain(AtomicReaderContext context, int doc)
+        throws IOException {
+      ComplexExplanation sumExpl = new ComplexExplanation();
+      sumExpl.setDescription("sum of:");
+      int coord = 0;
+      float sum = 0.0f;
+      boolean fail = false;
+      for (int i = 0; i < termWeights.length; i++) {
+
+        TermWeight w = termWeights[i];
+        if (w.scorer(context, ScorerContext.def().scoreDocsInOrder(true)
+            .topScorer(true)) == null) {
+          fail = true;
+          Explanation r = new Explanation(0.0f, "no match on required clause ("
+              + terms[i].toString() + ")");
+          sumExpl.addDetail(r);
+        }
+        Explanation e = w.explain(context, doc);
+        if (e.isMatch()) {
+          sumExpl.addDetail(e);
+          sum += e.getValue();
+          coord++;
+        } else {
+          Explanation r = new Explanation(0.0f, "no match on required clause ("
+              + terms[i].toString() + ")");
+          r.addDetail(e);
+          sumExpl.addDetail(r);
+          fail = true;
+        }
+      }
+      if (fail) {
+        sumExpl.setMatch(Boolean.FALSE);
+        sumExpl.setValue(0.0f);
+        sumExpl
+            .setDescription("Failure to meet condition(s) of required/prohibited clause(s)");
+        return sumExpl;
+      }
+      sumExpl.setMatch(0 < coord ? Boolean.TRUE : Boolean.FALSE);
+      sumExpl.setValue(sum);
+
+      final float coordFactor = disableCoord ? 1.0f
+          : coord(coord, terms.length);
+      if (coordFactor == 1.0f) {
+        return sumExpl; // eliminate wrapper
+      } else {
+        ComplexExplanation result = new ComplexExplanation(sumExpl.isMatch(),
+            sum * coordFactor, "product of:");
+        result.addDetail(sumExpl);
+        result.addDetail(new Explanation(coordFactor, "coord(" + coord + "/"
+            + terms.length + ")"));
+        return result;
+      }
+    }
+
+    @Override
+    public Query getQuery() {
+      return ConjunctionTermQuery.this;
+    }
+
+    @Override
+    public float getValueForNormalization() throws IOException {
+      float sum = 0.0f;
+      for (int i = 0; i < termWeights.length; i++) {
+        sum += termWeights[i].getValueForNormalization();
+      }
+      return sum * getBoost() * getBoost();
+    }
+
+    @Override
+    public void normalize(float norm, float topLevelBoost) {
+      topLevelBoost *= getBoost();
+      for (TermWeight w : termWeights) {
+        w.normalize(norm, topLevelBoost);
+      }
+    }
+
+    @Override
+    public Scorer scorer(AtomicReaderContext context,
+        ScorerContext scorerContext) throws IOException {
+      final DocsAndFreqs[] docsAndFreqs = new DocsAndFreqs[termWeights.length];
+      for (int i = 0; i < docsAndFreqs.length; i++) {
+        final Term term = terms[i].getTerm();
+        final String field = term.field();
+        final TermWeight weight = termWeights[i];
+        final TermState termState = weight.termStates.get(context.ord);
+        if (termState == null) {
+          return null;
+        }
+        final TermsEnum termsEnum = context.reader.terms(field)
+            .getThreadTermsEnum();
+        termsEnum.seekExact(term.bytes(), termState);
+        final ExactDocScorer docScorer = weight.similarity.exactDocScorer(
+            weight.stats, field, context);
+        docsAndFreqs[i] = new DocsAndFreqs(termsEnum.docs(
+            context.reader.getLiveDocs(), null), termsEnum.docFreq(), docScorer);
+      }
+      return new ConjunctionTermScorer(this, disableCoord ? 1.0f : coord(
+          termWeights.length, termWeights.length), docsAndFreqs);
+    }
+
+  }
+
+  /** Returns true iff <code>o</code> is equal to this. */
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ConjunctionTermQuery))
+      return false;
+    ConjunctionTermQuery other = (ConjunctionTermQuery) o;
+    return (this.getBoost() == other.getBoost())
+        && Arrays.equals(this.terms, other.terms)
+        && this.disableCoord == other.disableCoord;
+  }
+
+  /** Returns a hash code value for this object. */
+  @Override
+  public int hashCode() {
+    return Float.floatToIntBits(getBoost()) ^ Arrays.hashCode(terms)
+        + (disableCoord ? 17 : 0);
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/ConjunctionTermScorer.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/ConjunctionTermScorer.java	(revision 0)
+++ lucene/src/java/org/apache/lucene/search/ConjunctionTermScorer.java	(revision 0)
@@ -0,0 +1,122 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.index.DocsEnum;
+import org.apache.lucene.search.Similarity.ExactDocScorer;
+import org.apache.lucene.util.ArrayUtil;
+import java.io.IOException;
+import java.util.Comparator;
+
+/** Scorer for conjunctions, sets of terms, all of which are required. */
+final class ConjunctionTermScorer extends Scorer {
+
+  private final float coord;
+  private int lastDoc = -1;
+  private final DocsAndFreqs[] docsAndFreqs;
+  private final int leadIndex;
+  private final DocsAndFreqs lead;
+
+  public ConjunctionTermScorer(Weight weight, float coord,
+      DocsAndFreqs[] docsAndFreqs) throws IOException {
+    super(weight);
+    this.coord = coord;
+    this.docsAndFreqs = docsAndFreqs;
+    // Sort the array the first time...
+    // We don't need to sort the array in any future calls because we know
+    // it will already start off sorted (all scorers on same doc).
+
+    // Note that this comparator is not consistent with equals!
+    // Also we use mergeSort here to be stable (so order of Scoreres that
+    // match on first document keeps preserved):
+    ArrayUtil.mergeSort(docsAndFreqs, new Comparator<DocsAndFreqs>() { // sort
+                                                                       // the
+                                                                       // array
+          public int compare(DocsAndFreqs o1, DocsAndFreqs o2) {
+            return o1.freq - o2.freq;
+          }
+        });
+
+    leadIndex = 0;
+    lead = docsAndFreqs[leadIndex];
+
+  }
+
+  private int doNext(int doc) throws IOException {
+    if (doc == DocsEnum.NO_MORE_DOCS) {
+      return lastDoc = NO_MORE_DOCS;
+    }
+    do {
+      int i = 1;
+      for (i = 1; i < docsAndFreqs.length; i++) {
+        if (docsAndFreqs[i].doc < doc) {
+          docsAndFreqs[i].doc = docsAndFreqs[i].docs.advance(doc);
+        }
+        if (docsAndFreqs[i].doc > doc) {
+          break;
+        }
+      }
+      if (i == docsAndFreqs.length) {
+        return doc;
+      }
+      doc = lead.doc = lead.docs.nextDoc();
+      if (lead.doc == DocsEnum.NO_MORE_DOCS) {
+        return lastDoc = NO_MORE_DOCS;
+      }
+    } while (true);
+  }
+
+  @Override
+  public int advance(int target) throws IOException {
+    lead.doc = lead.docs.advance(target);
+    return lastDoc = doNext(lead.doc);
+  }
+
+  @Override
+  public int docID() {
+    return lastDoc;
+  }
+
+  @Override
+  public int nextDoc() throws IOException {
+    lead.doc = lead.docs.nextDoc();
+    return lastDoc = doNext(lead.doc);
+  }
+
+  @Override
+  public float score() throws IOException {
+    float sum = 0.0f;
+    for (DocsAndFreqs docs : docsAndFreqs) {
+      sum += docs.docScorer.score(lastDoc, docs.docs.freq());
+    }
+    return sum * coord;
+  }
+
+  static class DocsAndFreqs {
+    final DocsEnum docs;
+    final int freq;
+    final ExactDocScorer docScorer;
+    int doc = -1;
+
+    DocsAndFreqs(DocsEnum docs, int freq, ExactDocScorer docScorer) {
+      this.docs = docs;
+      this.freq = freq;
+      this.docScorer = docScorer;
+    }
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/TermQuery.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/TermQuery.java	(revision 1148635)
+++ lucene/src/java/org/apache/lucene/search/TermQuery.java	(working copy)
@@ -41,10 +41,10 @@
   private int docFreq;
   private transient TermContext perReaderTermState;
 
-  private class TermWeight extends Weight {
-    private final Similarity similarity;
-    private final Similarity.Stats stats;
-    private transient TermContext termStates;
+  class TermWeight extends Weight {
+    final Similarity similarity;
+    final Similarity.Stats stats;
+    transient TermContext termStates;
 
     public TermWeight(IndexSearcher searcher, TermContext termStates)
       throws IOException {
Index: lucene/src/test/org/apache/lucene/search/TestBooleanQuery.java
===================================================================
--- lucene/src/test/org/apache/lucene/search/TestBooleanQuery.java	(revision 1148635)
+++ lucene/src/test/org/apache/lucene/search/TestBooleanQuery.java	(working copy)
@@ -17,11 +17,11 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.index.IndexReader;
@@ -62,6 +62,30 @@
       // okay
     }
   }
+  
+  public void testRewriteToSpecialized() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random, dir);
+    for (int i = 0; i < 39; i++) {
+      Document doc = new Document();
+      doc.add(newField("field", "3" + i % 3 + " 2" + i % 2, Field.Store.NO,
+          Field.Index.ANALYZED));
+      w.addDocument(doc);
+    }
+    IndexReader r = w.getReader();
+    IndexSearcher s = newSearcher(r);
+    BooleanQuery bq1 = new BooleanQuery();
+    bq1.add(new TermQuery(new Term("field", "20")), BooleanClause.Occur.MUST);
+    bq1.add(new TermQuery(new Term("field", "30")), BooleanClause.Occur.MUST);
+    Query rewrite = bq1.rewrite(r);
+    assertTrue(rewrite instanceof ConjunctionTermQuery);
+    TopDocs search = s.search(bq1, 10);
+    assertEquals(7, search.totalHits);
+    r.close();
+    s.close();
+    w.close();
+    dir.close();
+  }
 
   // LUCENE-1630
   public void testNullOrSubScorer() throws Throwable {
