diff --git a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java
index adaf5ca6323a9..6eaec39d8fe03 100644
--- a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java
+++ b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java
@@ -24,6 +24,7 @@
 import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
 import org.apache.lucene.index.BinaryDocValues;
 import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
@@ -78,6 +79,7 @@
 import org.elasticsearch.index.query.QueryRewriteContext;
 import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.index.query.QueryShardException;
+import org.elasticsearch.index.query.Rewriteable;
 import org.elasticsearch.indices.breaker.CircuitBreakerService;
 import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
 
@@ -91,7 +93,6 @@
 import java.util.Objects;
 import java.util.function.Supplier;
 
-import static org.elasticsearch.percolator.PercolatorFieldMapper.parseQuery;
 import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;
 
 public class PercolateQueryBuilder extends AbstractQueryBuilder<PercolateQueryBuilder> {
@@ -646,9 +647,9 @@ protected Analyzer getWrappedAnalyzer(String fieldName) {
         PercolatorFieldMapper.FieldType pft = (PercolatorFieldMapper.FieldType) fieldType;
         String name = this.name != null ? this.name : pft.name();
         QueryShardContext percolateShardContext = wrap(context);
+        PercolatorFieldMapper.configureContext(percolateShardContext, pft.mapUnmappedFieldsAsText);;
         PercolateQuery.QueryStore queryStore = createStore(pft.queryBuilderField,
-            percolateShardContext,
-            pft.mapUnmappedFieldsAsText);
+            percolateShardContext);
 
         return pft.percolateQuery(name, queryStore, documents, docSearcher, excludeNestedDocuments, context.indexVersionCreated());
     }
@@ -695,8 +696,7 @@ static IndexSearcher createMultiDocumentSearcher(Analyzer analyzer, Collection<P
     }
 
     static PercolateQuery.QueryStore createStore(MappedFieldType queryBuilderFieldType,
-                                                 QueryShardContext context,
-                                                 boolean mapUnmappedFieldsAsString) {
+                                                 QueryShardContext context) {
         Version indexVersion = context.indexVersionCreated();
         NamedWriteableRegistry registry = context.getWriteableRegistry();
         return ctx -> {
@@ -723,7 +723,8 @@ static PercolateQuery.QueryStore createStore(MappedFieldType queryBuilderFieldTy
                                 assert valueLength > 0;
                                 QueryBuilder queryBuilder = input.readNamedWriteable(QueryBuilder.class);
                                 assert in.read() == -1;
-                                return PercolatorFieldMapper.toQuery(context, mapUnmappedFieldsAsString, queryBuilder);
+                                queryBuilder = Rewriteable.rewrite(queryBuilder, context);
+                                return queryBuilder.toQuery(context);
                             }
                         }
                     } else {
@@ -739,7 +740,10 @@ static PercolateQuery.QueryStore createStore(MappedFieldType queryBuilderFieldTy
                             try (XContentParser sourceParser = xContent
                                     .createParser(context.getXContentRegistry(), LoggingDeprecationHandler.INSTANCE,
                                         qbSource.bytes, qbSource.offset, qbSource.length)) {
-                                return parseQuery(context, mapUnmappedFieldsAsString, sourceParser);
+                                QueryBuilder queryBuilder = PercolatorFieldMapper.parseQueryBuilder(sourceParser,
+                                        sourceParser.getTokenLocation());
+                                queryBuilder = Rewriteable.rewrite(queryBuilder, context);
+                                return queryBuilder.toQuery(context);
                             }
                         } else {
                             return null;
@@ -755,6 +759,13 @@ static PercolateQuery.QueryStore createStore(MappedFieldType queryBuilderFieldTy
     static QueryShardContext wrap(QueryShardContext shardContext) {
         return new QueryShardContext(shardContext) {
 
+            @Override
+            public IndexReader getIndexReader() {
+                // The reader that matters in this context is not the reader of the shard but
+                // the reader of the MemoryIndex. We just use `null` for simplicity.
+                return null;
+            }
+
             @Override
             public BitSetProducer bitsetFilter(Query query) {
                 return context -> {
diff --git a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java
index 4f1f6d04d302c..7464e17aeb022 100644
--- a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java
+++ b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java
@@ -397,6 +397,8 @@ public void parse(ParseContext context) throws IOException {
             throw new IllegalArgumentException("a document can only contain one percolator query");
         }
 
+        configureContext(queryShardContext, isMapUnmappedFieldAsText());
+
         XContentParser parser = context.parser();
         QueryBuilder queryBuilder = parseQueryBuilder(
                 parser, parser.getTokenLocation()
@@ -410,7 +412,8 @@ public void parse(ParseContext context) throws IOException {
         Version indexVersion = context.mapperService().getIndexSettings().getIndexVersionCreated();
         createQueryBuilderField(indexVersion, queryBuilderField, queryBuilder, context);
 
-        Query query = toQuery(queryShardContext, isMapUnmappedFieldAsText(), queryBuilder);
+        QueryBuilder queryBuilderForProcessing = queryBuilder.rewrite(new QueryShardContext(queryShardContext));
+        Query query = queryBuilderForProcessing.toQuery(queryShardContext);
         processQuery(query, context);
     }
 
@@ -480,11 +483,7 @@ void processQuery(Query query, ParseContext context) {
         }
     }
 
-    static Query parseQuery(QueryShardContext context, boolean mapUnmappedFieldsAsString, XContentParser parser) throws IOException {
-        return toQuery(context, mapUnmappedFieldsAsString, parseQueryBuilder(parser, parser.getTokenLocation()));
-    }
-
-    static Query toQuery(QueryShardContext context, boolean mapUnmappedFieldsAsString, QueryBuilder queryBuilder) throws IOException {
+    static void configureContext(QueryShardContext context, boolean mapUnmappedFieldsAsString) {
         // This means that fields in the query need to exist in the mapping prior to registering this query
         // The reason that this is required, is that if a field doesn't exist then the query assumes defaults, which may be undesired.
         //
@@ -499,10 +498,9 @@ static Query toQuery(QueryShardContext context, boolean mapUnmappedFieldsAsStrin
         // as an analyzed string.
         context.setAllowUnmappedFields(false);
         context.setMapUnmappedFieldAsString(mapUnmappedFieldsAsString);
-        return queryBuilder.toQuery(context);
     }
 
-    private static QueryBuilder parseQueryBuilder(XContentParser parser, XContentLocation location) {
+    static QueryBuilder parseQueryBuilder(XContentParser parser, XContentLocation location) {
         try {
             return parseInnerQueryBuilder(parser);
         } catch (IOException e) {
diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/QueryBuilderStoreTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/QueryBuilderStoreTests.java
index 1c7ae3681ac63..ff9688487d32d 100644
--- a/modules/percolator/src/test/java/org/elasticsearch/percolator/QueryBuilderStoreTests.java
+++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/QueryBuilderStoreTests.java
@@ -38,10 +38,12 @@
 import org.elasticsearch.index.fielddata.plain.BytesBinaryDVIndexFieldData;
 import org.elasticsearch.index.mapper.BinaryFieldMapper;
 import org.elasticsearch.index.mapper.ContentPath;
+import org.elasticsearch.index.mapper.KeywordFieldMapper;
 import org.elasticsearch.index.mapper.Mapper;
 import org.elasticsearch.index.mapper.ParseContext;
 import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.index.query.TermQueryBuilder;
+import org.elasticsearch.mock.orig.Mockito;
 import org.elasticsearch.search.SearchModule;
 import org.elasticsearch.test.ESTestCase;
 
@@ -93,7 +95,14 @@ public void testStoringQueryBuilders() throws IOException {
             when(queryShardContext.getXContentRegistry()).thenReturn(xContentRegistry());
             when(queryShardContext.getForField(fieldMapper.fieldType()))
                 .thenReturn(new BytesBinaryDVIndexFieldData(new Index("index", "uuid"), fieldMapper.name()));
-            PercolateQuery.QueryStore queryStore = PercolateQueryBuilder.createStore(fieldMapper.fieldType(), queryShardContext, false);
+            when(queryShardContext.fieldMapper(Mockito.anyString())).thenAnswer(invocation -> {
+                final String fieldName = (String) invocation.getArguments()[0];
+                KeywordFieldMapper.KeywordFieldType ft = new KeywordFieldMapper.KeywordFieldType();
+                ft.setName(fieldName);
+                ft.freeze();
+                return ft;
+            });
+            PercolateQuery.QueryStore queryStore = PercolateQueryBuilder.createStore(fieldMapper.fieldType(), queryShardContext);
 
             try (IndexReader indexReader = DirectoryReader.open(directory)) {
                 LeafReaderContext leafContext = indexReader.leaves().get(0);
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ConstantFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/ConstantFieldType.java
new file mode 100644
index 0000000000000..779e8f91350dc
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/index/mapper/ConstantFieldType.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+package org.elasticsearch.index.mapper;
+
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.lucene.search.Queries;
+import org.elasticsearch.common.regex.Regex;
+import org.elasticsearch.index.query.QueryShardContext;
+
+import java.util.List;
+
+/**
+ * A {@link MappedFieldType} that has the same value for all documents.
+ * Factory methods for queries are called at rewrite time so they should be
+ * cheap. In particular they should not read data from disk or perform a
+ * network call. Furthermore they may only return a {@link MatchAllDocsQuery}
+ * or a {@link MatchNoDocsQuery}.
+ */
+public abstract class ConstantFieldType extends MappedFieldType {
+
+    public ConstantFieldType() {
+        super();
+    }
+
+    public ConstantFieldType(ConstantFieldType other) {
+        super(other);
+    }
+
+    @Override
+    public final boolean isSearchable() {
+        return true;
+    }
+
+    @Override
+    public final boolean isAggregatable() {
+        return true;
+    }
+
+    @Override
+    public final Query existsQuery(QueryShardContext context) {
+        return new MatchAllDocsQuery();
+    }
+
+    /**
+     * Return whether the constant value of this field matches the provided {@code pattern}
+     * as documented in {@link Regex#simpleMatch}.
+     */
+    protected abstract boolean matches(String pattern, QueryShardContext context);
+
+    private static String valueToString(Object value) {
+        return value instanceof BytesRef
+                ? ((BytesRef) value).utf8ToString()
+                : value.toString();
+    }
+
+    @Override
+    public final Query termQuery(Object value, QueryShardContext context) {
+        String pattern = valueToString(value);
+        if (matches(pattern, context)) {
+            return Queries.newMatchAllQuery();
+        } else {
+            return new MatchNoDocsQuery();
+        }
+    }
+
+    @Override
+    public final Query termsQuery(List<?> values, QueryShardContext context) {
+        for (Object value : values) {
+            String pattern = valueToString(value);
+            if (matches(pattern, context)) {
+                // `terms` queries are a disjunction, so one matching term is enough
+                return Queries.newMatchAllQuery();
+            }
+        }
+        return new MatchNoDocsQuery();
+    }
+
+    @Override
+    public final Query prefixQuery(String prefix,
+                             @Nullable MultiTermQuery.RewriteMethod method,
+                             QueryShardContext context) {
+        String pattern = prefix + "*";
+        if (matches(pattern, context)) {
+            return Queries.newMatchAllQuery();
+        } else {
+            return new MatchNoDocsQuery();
+        }
+    }
+
+    @Override
+    public final Query wildcardQuery(String value,
+                               @Nullable MultiTermQuery.RewriteMethod method,
+                               QueryShardContext context) {
+        if (matches(value, context)) {
+            return Queries.newMatchAllQuery();
+        } else {
+            return new MatchNoDocsQuery();
+        }
+    }
+
+}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java
index c3693f4ded9f3..603b8b22216aa 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/IndexFieldMapper.java
@@ -21,13 +21,7 @@
 
 import org.apache.lucene.index.IndexOptions;
 import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.util.BytesRef;
-import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.lucene.Lucene;
-import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.index.fielddata.IndexFieldData;
@@ -89,7 +83,7 @@ public MetadataFieldMapper getDefault(MappedFieldType fieldType, ParserContext c
         }
     }
 
-    static final class IndexFieldType extends MappedFieldType {
+    static final class IndexFieldType extends ConstantFieldType {
 
         IndexFieldType() {}
 
@@ -108,81 +102,8 @@ public String typeName() {
         }
 
         @Override
-        public boolean isSearchable() {
-            // The _index field is always searchable.
-            return true;
-        }
-
-        @Override
-        public Query existsQuery(QueryShardContext context) {
-            return new MatchAllDocsQuery();
-        }
-
-        /**
-         * This termQuery impl looks at the context to determine the index that
-         * is being queried and then returns a MATCH_ALL_QUERY or MATCH_NO_QUERY
-         * if the value matches this index. This can be useful if aliases or
-         * wildcards are used but the aim is to restrict the query to specific
-         * indices
-         */
-        @Override
-        public Query termQuery(Object value, @Nullable QueryShardContext context) {
-            String pattern = value instanceof BytesRef
-                ? ((BytesRef) value).utf8ToString()
-                : value.toString();
-            if (context.indexMatches(pattern)) {
-                // No need to OR these clauses - we can only logically be
-                // running in the context of just one of these index names.
-                return Queries.newMatchAllQuery();
-            } else {
-                return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
-                    "] doesn't match the provided value [" + value + "].");
-            }
-        }
-
-        @Override
-        public Query termsQuery(List values, QueryShardContext context) {
-            if (context == null) {
-                return super.termsQuery(values, context);
-            }
-            for (Object value : values) {
-                String pattern = value instanceof BytesRef
-                    ? ((BytesRef) value).utf8ToString()
-                    : value.toString();
-                if (context.indexMatches(pattern)) {
-                    // No need to OR these clauses - we can only logically be
-                    // running in the context of just one of these index names.
-                    return Queries.newMatchAllQuery();
-                }
-            }
-            // None of the listed index names are this one
-            return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
-                "] doesn't match the provided values [" + values + "].");
-        }
-
-        @Override
-        public Query prefixQuery(String value,
-                                 @Nullable MultiTermQuery.RewriteMethod method,
-                                 QueryShardContext context) {
-            String pattern = value + "*";
-            if (context.indexMatches(pattern)) {
-                return Queries.newMatchAllQuery();
-            } else {
-                return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName() +
-                    "] doesn't match the provided prefix [" + value + "].");
-            }
-        }
-
-        @Override
-        public Query wildcardQuery(String value,
-                                   @Nullable MultiTermQuery.RewriteMethod method,
-                                   QueryShardContext context) {
-            if (context.indexMatches(value)) {
-                return Queries.newMatchAllQuery();
-            } else {
-                return Queries.newMatchNoDocsQuery("The index [" + context.getFullyQualifiedIndex().getName()
-                    + "] doesn't match the provided pattern [" + value + "].");
-            }
+        protected boolean matches(String pattern, QueryShardContext context) {
+            return context.indexMatches(pattern);
         }
 
         @Override
diff --git a/server/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java
index d5cc101ede8ad..2e027e21b8f5f 100644
--- a/server/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java
@@ -20,6 +20,7 @@
 package org.elasticsearch.index.query;
 
 import org.apache.lucene.search.BoostQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spans.SpanBoostQuery;
 import org.apache.lucene.search.spans.SpanQuery;
@@ -103,7 +104,7 @@ public final Query toQuery(QueryShardContext context) throws IOException {
             if (boost != DEFAULT_BOOST) {
                 if (query instanceof SpanQuery) {
                     query = new SpanBoostQuery((SpanQuery) query, boost);
-                } else {
+                } else if (query instanceof MatchNoDocsQuery == false) {
                     query = new BoostQuery(query, boost);
                 }
             }
@@ -232,7 +233,7 @@ static Collection<Query> toQueries(Collection<QueryBuilder> queryBuilders, Query
             IOException {
         List<Query> queries = new ArrayList<>(queryBuilders.size());
         for (QueryBuilder queryBuilder : queryBuilders) {
-            Query query = queryBuilder.toQuery(context);
+            Query query = queryBuilder.rewrite(context).toQuery(context);
             if (query != null) {
                 queries.add(query);
             }
diff --git a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java
index 8df0fec044124..bd3048da29bb4 100644
--- a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java
@@ -19,7 +19,6 @@
 
 package org.elasticsearch.index.query;
 
-import org.apache.lucene.index.Term;
 import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.Query;
@@ -28,7 +27,6 @@
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.common.unit.Fuzziness;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -322,18 +320,26 @@ public String getWriteableName() {
         return NAME;
     }
 
+    @Override
+    protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null) {
+            MappedFieldType fieldType = context.fieldMapper(fieldName);
+            if (fieldType == null) {
+                return new MatchNoneQueryBuilder();
+            }
+        }
+        return super.doRewrite(context);
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        Query query = null;
-        String rewrite = this.rewrite;
         MappedFieldType fieldType = context.fieldMapper(fieldName);
-        if (fieldType != null) {
-            query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context);
-        }
-        if (query == null) {
-            int maxEdits = fuzziness.asDistance(BytesRefs.toString(value));
-            query = new FuzzyQuery(new Term(fieldName, BytesRefs.toBytesRef(value)), maxEdits, prefixLength, maxExpansions, transpositions);
+        if (fieldType == null) {
+            throw new IllegalStateException("Rewrite first");
         }
+        String rewrite = this.rewrite;
+        Query query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context);
         if (query instanceof MultiTermQuery) {
             MultiTermQuery.RewriteMethod rewriteMethod = QueryParsers.parseRewriteMethod(rewrite, null,
                 LoggingDeprecationHandler.INSTANCE);
diff --git a/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
index 304f17e88cb55..1ea6adb14d5b4 100644
--- a/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
@@ -29,7 +29,6 @@
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.logging.DeprecationLogger;
-import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
@@ -169,31 +168,39 @@ public String getWriteableName() {
         return NAME;
     }
 
+    @Override
+    protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
+        if (ids.isEmpty()) {
+            return new MatchNoneQueryBuilder();
+        }
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null && context.fieldMapper(IdFieldMapper.NAME) == null) {
+            // no mappings yet
+            return new MatchNoneQueryBuilder();
+        }
+        return super.doRewrite(queryRewriteContext);
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
         MappedFieldType idField = context.fieldMapper(IdFieldMapper.NAME);
-        if (idField == null) {
-            return new MatchNoDocsQuery("No mappings");
+        if (idField == null || ids.isEmpty()) {
+            throw new IllegalStateException("Rewrite first");
         }
-        if (this.ids.isEmpty()) {
-             return Queries.newMatchNoDocsQuery("Missing ids in \"" + this.getName() + "\" query.");
+        final DocumentMapper mapper = context.getMapperService().documentMapper();
+        Collection<String> typesForQuery;
+        if (types.length == 0) {
+            typesForQuery = context.queryTypes();
+        } else if (types.length == 1 && MetaData.ALL.equals(types[0])) {
+            typesForQuery = Collections.singleton(mapper.type());
         } else {
-            final DocumentMapper mapper = context.getMapperService().documentMapper();
-            Collection<String> typesForQuery;
-            if (types.length == 0) {
-                typesForQuery = context.queryTypes();
-            } else if (types.length == 1 && MetaData.ALL.equals(types[0])) {
-                typesForQuery = Collections.singleton(mapper.type());
-            } else {
-                typesForQuery = new HashSet<>(Arrays.asList(types));
-            }
+            typesForQuery = new HashSet<>(Arrays.asList(types));
+        }
 
-            if (typesForQuery.contains(mapper.type())) {
-                return idField.termsQuery(new ArrayList<>(ids), context);
-            } else {
-                return new MatchNoDocsQuery("Type mismatch");
-            }
-            
+        if (typesForQuery.contains(mapper.type())) {
+            return idField.termsQuery(new ArrayList<>(ids), context);
+        } else {
+            return new MatchNoDocsQuery("Type mismatch");
         }
     }
 
diff --git a/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java
index db596e2ecfc7b..44c8dd44b49c8 100644
--- a/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java
@@ -19,20 +19,20 @@
 
 package org.elasticsearch.index.query;
 
-import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.ConstantFieldType;
 import org.elasticsearch.index.query.support.QueryParsers;
 
 import java.io.IOException;
@@ -171,14 +171,26 @@ public String getWriteableName() {
     
     @Override
     protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
-        if ("_index".equals(fieldName)) {
-            // Special-case optimisation for canMatch phase:  
-            // We can skip querying this shard if the index name doesn't match the value of this query on the "_index" field.
-            QueryShardContext shardContext = queryRewriteContext.convertToShardContext();
-            if (shardContext != null && shardContext.indexMatches(value + "*") == false) {
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null) {
+            MappedFieldType fieldType = context.fieldMapper(this.fieldName);
+            if (fieldType == null) {
                 return new MatchNoneQueryBuilder();
-            }            
+            } else if (fieldType instanceof ConstantFieldType) {
+                // This logic is correct for all field types, but by only applying it to constant
+                // fields we also have the guarantee that it doesn't perform I/O, which is important
+                // since rewrites might happen on a network thread.
+                Query query = fieldType.prefixQuery(value, null, context); // the rewrite method doesn't matter
+                if (query instanceof MatchAllDocsQuery) {
+                    return new MatchAllQueryBuilder();
+                } else if (query instanceof MatchNoDocsQuery) {
+                    return new MatchNoneQueryBuilder();
+                } else {
+                    assert false : "Constant fields must produce match-all or match-none queries, got " + query ;
+                }
+            }
         }
+
         return super.doRewrite(queryRewriteContext);
     }    
 
@@ -186,20 +198,11 @@ protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws
     protected Query doToQuery(QueryShardContext context) throws IOException {
         MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(rewrite, null, LoggingDeprecationHandler.INSTANCE);
 
-        Query query = null;
         MappedFieldType fieldType = context.fieldMapper(fieldName);
-        if (fieldType != null) {
-            query = fieldType.prefixQuery(value, method, context);
+        if (fieldType == null) {
+            throw new IllegalStateException("Rewrite first");
         }
-        if (query == null) {
-            PrefixQuery prefixQuery = new PrefixQuery(new Term(fieldName, BytesRefs.toBytesRef(value)));
-            if (method != null) {
-                prefixQuery.setRewriteMethod(method);
-            }
-            query = prefixQuery;
-        }
-
-        return query;
+        return fieldType.prefixQuery(value, method, context);
     }
 
     @Override
diff --git a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java
index 156fa27264e1f..6cb1704611ba0 100644
--- a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java
@@ -21,7 +21,6 @@
 
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.ParsingException;
@@ -29,14 +28,12 @@
 import org.elasticsearch.common.geo.ShapeRelation;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.common.time.DateFormatter;
 import org.elasticsearch.common.time.DateMathParser;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.index.mapper.MapperService;
 
 import java.io.IOException;
 import java.time.DateTimeException;
@@ -432,21 +429,23 @@ public String getWriteableName() {
     // Overridable for testing only
     protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteContext) throws IOException {
         QueryShardContext shardContext = queryRewriteContext.convertToShardContext();
-        // If the context is null we are not on the shard and cannot
-        // rewrite so just pretend there is an intersection so that the rewrite is a noop
-        if (shardContext == null || shardContext.getIndexReader() == null) {
-            return MappedFieldType.Relation.INTERSECTS;
-        }
-        final MapperService mapperService = shardContext.getMapperService();
-        final MappedFieldType fieldType = mapperService.fieldType(fieldName);
-        if (fieldType == null) {
-            // no field means we have no values
-            return MappedFieldType.Relation.DISJOINT;
-        } else {
+        if (shardContext != null) {
+            final MappedFieldType fieldType = shardContext.fieldMapper(fieldName);
+            if (fieldType == null) {
+                return MappedFieldType.Relation.DISJOINT;
+            }
+            if (shardContext.getIndexReader() == null) {
+                // No reader, this may happen e.g. for percolator queries.
+                return MappedFieldType.Relation.INTERSECTS;
+            }
+
             DateMathParser dateMathParser = getForceDateParser();
             return fieldType.isFieldWithinQuery(shardContext.getIndexReader(), from, to, includeLower,
                     includeUpper, timeZone, dateMathParser, queryRewriteContext);
         }
+
+        // Not on the shard, we have no way to know what the relation is.
+        return MappedFieldType.Relation.INTERSECTS;
     }
 
     @Override
@@ -490,26 +489,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
                 return ExistsQueryBuilder.newFilter(context, fieldName);
             }
         }
-        Query query = null;
         MappedFieldType mapper = context.fieldMapper(this.fieldName);
-        if (mapper != null) {
-            DateMathParser forcedDateParser = getForceDateParser();
-            query = mapper.rangeQuery(
-                    from, to, includeLower, includeUpper,
-                    relation, timeZone, forcedDateParser, context);
-        } else {
-            if (timeZone != null) {
-                throw new QueryShardException(context, "[range] time_zone can not be applied to non unmapped field ["
-                        + fieldName + "]");
-            }
-        }
-
-        if (query == null) {
-            query = new TermRangeQuery(this.fieldName,
-                    BytesRefs.toBytesRef(from), BytesRefs.toBytesRef(to),
-                    includeLower, includeUpper);
+        if (mapper == null) {
+            throw new IllegalStateException("Rewrite first");
         }
-        return query;
+        DateMathParser forcedDateParser = getForceDateParser();
+        return mapper.rangeQuery(
+                from, to, includeLower, includeUpper,
+                relation, timeZone, forcedDateParser, context);
     }
 
     @Override
diff --git a/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java
index 49e5e53e1ed91..d55f9bceaa9bd 100644
--- a/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java
@@ -126,11 +126,16 @@ public static SpanMultiTermQueryBuilder fromXContent(XContentParser parser) thro
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        if (multiTermQueryBuilder instanceof PrefixQueryBuilder) {
+        // We do the rewrite in toQuery to not have to deal with the case when a multi-term builder rewrites to a non-multi-term
+        // builder.
+        QueryBuilder multiTermQueryBuilder = Rewriteable.rewrite(this.multiTermQueryBuilder, context);
+        if (multiTermQueryBuilder instanceof MatchNoneQueryBuilder) {
+            return new SpanMatchNoDocsQuery(this.multiTermQueryBuilder.fieldName(), "Inner query rewrote to match_none");
+        } else if (multiTermQueryBuilder instanceof PrefixQueryBuilder) {
             PrefixQueryBuilder prefixBuilder = (PrefixQueryBuilder) multiTermQueryBuilder;
-            MappedFieldType fieldType = context.fieldMapper(multiTermQueryBuilder.fieldName());
+            MappedFieldType fieldType = context.fieldMapper(prefixBuilder.fieldName());
             if (fieldType == null) {
-                return new SpanMatchNoDocsQuery(multiTermQueryBuilder.fieldName(), "unknown field");
+                throw new IllegalStateException("Rewrite first");
             }
             final SpanMultiTermQueryWrapper.SpanRewriteMethod spanRewriteMethod;
             if (prefixBuilder.rewrite() != null) {
@@ -159,7 +164,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
                 }
             }
             if (subQuery instanceof MatchNoDocsQuery) {
-                return new SpanMatchNoDocsQuery(multiTermQueryBuilder.fieldName(), subQuery.toString());
+                return new SpanMatchNoDocsQuery(this.multiTermQueryBuilder.fieldName(), subQuery.toString());
             } else if (subQuery instanceof MultiTermQuery == false) {
                 throw new UnsupportedOperationException("unsupported inner query, should be "
                     + MultiTermQuery.class.getName() + " but was " + subQuery.getClass().getName());
diff --git a/server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java
index 262bfb2c6b5b3..8a0118d26e8db 100644
--- a/server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/TermQueryBuilder.java
@@ -19,15 +19,15 @@
 
 package org.elasticsearch.index.query;
 
-import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.ConstantFieldType;
 
 import java.io.IOException;
 
@@ -132,28 +132,35 @@ public static TermQueryBuilder fromXContent(XContentParser parser) throws IOExce
     
     @Override
     protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
-        if ("_index".equals(fieldName)) {
-            // Special-case optimisation for canMatch phase:  
-            // We can skip querying this shard if the index name doesn't match the value of this query on the "_index" field.
-            QueryShardContext shardContext = queryRewriteContext.convertToShardContext();
-            if (shardContext != null && shardContext.indexMatches(BytesRefs.toString(value)) == false) {
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null) {
+            MappedFieldType fieldType = context.fieldMapper(this.fieldName);
+            if (fieldType == null) {
                 return new MatchNoneQueryBuilder();
-            }            
+            } else if (fieldType instanceof ConstantFieldType) {
+                // This logic is correct for all field types, but by only applying it to constant
+                // fields we also have the guarantee that it doesn't perform I/O, which is important
+                // since rewrites might happen on a network thread.
+                Query query = fieldType.termQuery(value, context);
+                if (query instanceof MatchAllDocsQuery) {
+                    return new MatchAllQueryBuilder();
+                } else if (query instanceof MatchNoDocsQuery) {
+                    return new MatchNoneQueryBuilder();
+                } else {
+                    assert false : "Constant fields must produce match-all or match-none queries, got " + query ;
+                }
+            }
         }
         return super.doRewrite(queryRewriteContext);
     }
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        Query query = null;
         MappedFieldType mapper = context.fieldMapper(this.fieldName);
-        if (mapper != null) {
-            query = mapper.termQuery(this.value, context);
-        }
-        if (query == null) {
-            query = new TermQuery(new Term(this.fieldName, BytesRefs.toBytesRef(this.value)));
+        if (mapper == null) {
+            throw new IllegalStateException("Rewrite first");
         }
-        return query;
+        return mapper.termQuery(this.value, context);
     }
 
     @Override
diff --git a/server/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java
index 4cf205ea5b240..48aaa4cb8249c 100644
--- a/server/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/TermsQueryBuilder.java
@@ -20,8 +20,9 @@
 package org.elasticsearch.index.query;
 
 import org.apache.logging.log4j.LogManager;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermInSetQuery;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.lucene.util.SetOnce;
@@ -35,13 +36,12 @@
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.logging.DeprecationLogger;
-import org.elasticsearch.common.lucene.BytesRefs;
-import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.ConstantFieldType;
 import org.elasticsearch.indices.TermsLookup;
 
 import java.io.IOException;
@@ -432,12 +432,9 @@ public String getWriteableName() {
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        if (termsLookup != null || supplier != null) {
+        if (termsLookup != null || supplier != null || values == null || values.isEmpty()) {
             throw new UnsupportedOperationException("query must be rewritten first");
         }
-        if (values == null || values.isEmpty()) {
-            return Queries.newMatchNoDocsQuery("No terms supplied for \"" + getName() + "\" query.");
-        }
         int maxTermsCount = context.getIndexSettings().getMaxTermsCount();
         if (values.size() > maxTermsCount){
             throw new IllegalArgumentException(
@@ -446,16 +443,10 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
                     IndexSettings.MAX_TERMS_COUNT_SETTING.getKey() + "] index level setting.");
         }
         MappedFieldType fieldType = context.fieldMapper(fieldName);
-
-        if (fieldType != null) {
-            return fieldType.termsQuery(values, context);
-        } else {
-            BytesRef[] filterValues = new BytesRef[values.size()];
-            for (int i = 0; i < filterValues.length; i++) {
-                filterValues[i] = BytesRefs.toBytesRef(values.get(i));
-            }
-            return new TermInSetQuery(fieldName, filterValues);
+        if (fieldType == null) {
+            throw new IllegalStateException("Rewrite first");
         }
+        return fieldType.termsQuery(values, context);
     }
 
     private void fetch(TermsLookup termsLookup, Client client, ActionListener<List<Object>> actionListener) {
@@ -499,21 +490,31 @@ protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) {
             })));
             return new TermsQueryBuilder(this.fieldName, supplier::get);
         }
-        if ("_index".equals(this.fieldName) && values != null) {
-            // Special-case optimisation for canMatch phase:  
-            // We can skip querying this shard if the index name doesn't match any of the search terms.
-            QueryShardContext shardContext = queryRewriteContext.convertToShardContext();
-            if (shardContext != null) {
-                for (Object localValue : values) {
-                    if (shardContext.indexMatches(BytesRefs.toString(localValue))) {
-                        // We can match - at least one index name matches
-                        return this;
-                    }     
-                }
-                // all index names are invalid - no possibility of a match on this shard.
+
+        if (values == null || values.isEmpty()) {
+            return new MatchNoneQueryBuilder();
+        }
+
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null) {
+            MappedFieldType fieldType = context.fieldMapper(this.fieldName);
+            if (fieldType == null) {
                 return new MatchNoneQueryBuilder();
+            } else if (fieldType instanceof ConstantFieldType) {
+                // This logic is correct for all field types, but by only applying it to constant
+                // fields we also have the guarantee that it doesn't perform I/O, which is important
+                // since rewrites might happen on a network thread.
+                Query query = fieldType.termsQuery(values, context);
+                if (query instanceof MatchAllDocsQuery) {
+                    return new MatchAllQueryBuilder();
+                } else if (query instanceof MatchNoDocsQuery) {
+                    return new MatchNoneQueryBuilder();
+                } else {
+                    assert false : "Constant fields must produce match-all or match-none queries, got " + query ;
+                }
             }
         }
+
         return this;
     }
 }
diff --git a/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java
index 115fa8d476dfd..39ca3b0a45b6f 100644
--- a/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java
@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.Query;
@@ -27,11 +28,11 @@
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.ConstantFieldType;
 import org.elasticsearch.index.query.support.QueryParsers;
 
 import java.io.IOException;
@@ -182,14 +183,26 @@ public static WildcardQueryBuilder fromXContent(XContentParser parser) throws IO
     
     @Override
     protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
-        if ("_index".equals(fieldName)) {
-            // Special-case optimisation for canMatch phase:  
-            // We can skip querying this shard if the index name doesn't match the value of this query on the "_index" field.
-            QueryShardContext shardContext = queryRewriteContext.convertToShardContext();
-            if (shardContext != null && shardContext.indexMatches(BytesRefs.toString(value)) == false) {
+        QueryShardContext context = queryRewriteContext.convertToShardContext();
+        if (context != null) {
+            MappedFieldType fieldType = context.fieldMapper(this.fieldName);
+            if (fieldType == null) {
                 return new MatchNoneQueryBuilder();
-            }            
+            } else if (fieldType instanceof ConstantFieldType) {
+                // This logic is correct for all field types, but by only applying it to constant
+                // fields we also have the guarantee that it doesn't perform I/O, which is important
+                // since rewrites might happen on a network thread.
+                Query query = fieldType.wildcardQuery(value, null, context); // the rewrite method doesn't matter
+                if (query instanceof MatchAllDocsQuery) {
+                    return new MatchAllQueryBuilder();
+                } else if (query instanceof MatchNoDocsQuery) {
+                    return new MatchNoneQueryBuilder();
+                } else {
+                    assert false : "Constant fields must produce match-all or match-none queries, got " + query ;
+                }
+            }
         }
+
         return super.doRewrite(queryRewriteContext);
     }    
 
@@ -198,7 +211,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
         MappedFieldType fieldType = context.fieldMapper(fieldName);
 
         if (fieldType == null) {
-            return new MatchNoDocsQuery("unknown field [" + fieldName + "]");
+            throw new IllegalStateException("Rewrite first");
         }
 
         MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregationBuilder.java
index 81d1b0445b3e2..45a755d07b286 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregationBuilder.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/significant/SignificantTermsAggregationBuilder.java
@@ -26,6 +26,7 @@
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryRewriteContext;
 import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
@@ -127,10 +128,23 @@ protected SignificantTermsAggregationBuilder(SignificantTermsAggregationBuilder
     }
 
     @Override
-    protected AggregationBuilder shallowCopy(Builder factoriesBuilder, Map<String, Object> metaData) {
+    protected SignificantTermsAggregationBuilder shallowCopy(Builder factoriesBuilder, Map<String, Object> metaData) {
         return new SignificantTermsAggregationBuilder(this, factoriesBuilder, metaData);
     }
 
+    @Override
+    protected AggregationBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException {
+        if (filterBuilder != null) {
+            QueryBuilder rewrittenFilter = filterBuilder.rewrite(queryShardContext);
+            if (rewrittenFilter != filterBuilder) {
+                SignificantTermsAggregationBuilder rewritten = shallowCopy(factoriesBuilder, metaData);
+                rewritten.backgroundFilter(rewrittenFilter);
+                return rewritten;
+            }
+        }
+        return super.doRewrite(queryShardContext);
+    }
+
     @Override
     protected void innerWriteTo(StreamOutput out) throws IOException {
         bucketCountThresholds.writeTo(out);
diff --git a/server/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java
index 97aa6f0d40589..9fc49ab0f5db5 100644
--- a/server/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java
@@ -22,6 +22,7 @@
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -85,7 +86,7 @@ protected void doAssertLuceneQuery(BoolQueryBuilder queryBuilder, Query query, Q
 
             if (clauses.isEmpty()) {
                 assertThat(query, instanceOf(MatchAllDocsQuery.class));
-            } else {
+            } else if (query instanceof MatchNoDocsQuery == false) {
                 assertThat(query, instanceOf(BooleanQuery.class));
                 BooleanQuery booleanQuery = (BooleanQuery) query;
                 if (queryBuilder.adjustPureNegative()) {
@@ -113,7 +114,7 @@ private static List<BooleanClause> getBooleanClauses(List<QueryBuilder> queryBui
                                                             BooleanClause.Occur occur, QueryShardContext context) throws IOException {
         List<BooleanClause> clauses = new ArrayList<>();
         for (QueryBuilder query : queryBuilders) {
-            Query innerQuery = query.toQuery(context);
+            Query innerQuery = query.rewrite(context).toQuery(context);
             if (innerQuery != null) {
                 clauses.add(new BooleanClause(innerQuery, occur));
             }
@@ -195,15 +196,15 @@ public void testMinShouldMatchFilterWithoutShouldClauses() throws Exception {
     public void testMinShouldMatchBiggerThanNumberOfShouldClauses() throws Exception {
         BooleanQuery bq = (BooleanQuery) parseQuery(
             boolQuery()
-                .should(termQuery("foo", "bar"))
-                .should(termQuery("foo2", "bar2"))
+                .should(termQuery(STRING_FIELD_NAME, "bar"))
+                .should(termQuery(STRING_FIELD_NAME_2, "bar2"))
                 .minimumShouldMatch("3")).toQuery(createShardContext());
         assertEquals(3, bq.getMinimumNumberShouldMatch());
 
         bq = (BooleanQuery) parseQuery(
             boolQuery()
-                .should(termQuery("foo", "bar"))
-                .should(termQuery("foo2", "bar2"))
+                .should(termQuery(STRING_FIELD_NAME, "bar"))
+                .should(termQuery(STRING_FIELD_NAME_2, "bar2"))
                 .minimumShouldMatch(3)).toQuery(createShardContext());
         assertEquals(3, bq.getMinimumNumberShouldMatch());
     }
@@ -211,8 +212,8 @@ public void testMinShouldMatchBiggerThanNumberOfShouldClauses() throws Exception
     public void testMinShouldMatchDisableCoord() throws Exception {
         BooleanQuery bq = (BooleanQuery) parseQuery(
                 boolQuery()
-                        .should(termQuery("foo", "bar"))
-                        .should(termQuery("foo2", "bar2"))
+                        .should(termQuery(STRING_FIELD_NAME, "bar"))
+                        .should(termQuery(STRING_FIELD_NAME, "bar2"))
                         .minimumShouldMatch("3")).toQuery(createShardContext());
         assertEquals(3, bq.getMinimumNumberShouldMatch());
     }
@@ -292,22 +293,22 @@ public void testRewrite() throws IOException {
         boolean mustRewrite = false;
         if (randomBoolean()) {
             mustRewrite = true;
-            boolQueryBuilder.must(new WrapperQueryBuilder(new TermsQueryBuilder("foo", "must").toString()));
+            boolQueryBuilder.must(new WrapperQueryBuilder(new TermsQueryBuilder(STRING_FIELD_NAME, "must").toString()));
         }
         if (randomBoolean()) {
             mustRewrite = true;
-            boolQueryBuilder.should(new WrapperQueryBuilder(new TermsQueryBuilder("foo", "should").toString()));
+            boolQueryBuilder.should(new WrapperQueryBuilder(new TermsQueryBuilder(STRING_FIELD_NAME, "should").toString()));
         }
         if (randomBoolean()) {
             mustRewrite = true;
-            boolQueryBuilder.filter(new WrapperQueryBuilder(new TermsQueryBuilder("foo", "filter").toString()));
+            boolQueryBuilder.filter(new WrapperQueryBuilder(new TermsQueryBuilder(STRING_FIELD_NAME, "filter").toString()));
         }
         if (randomBoolean()) {
             mustRewrite = true;
-            boolQueryBuilder.mustNot(new WrapperQueryBuilder(new TermsQueryBuilder("foo", "must_not").toString()));
+            boolQueryBuilder.mustNot(new WrapperQueryBuilder(new TermsQueryBuilder(STRING_FIELD_NAME, "must_not").toString()));
         }
         if (mustRewrite == false && randomBoolean()) {
-            boolQueryBuilder.must(new TermsQueryBuilder("foo", "no_rewrite"));
+            boolQueryBuilder.must(new TermsQueryBuilder(STRING_FIELD_NAME, "no_rewrite"));
         }
         QueryBuilder rewritten = boolQueryBuilder.rewrite(createShardContext());
         if (mustRewrite == false && boolQueryBuilder.must().isEmpty()) {
@@ -318,16 +319,16 @@ public void testRewrite() throws IOException {
             if (mustRewrite) {
                 assertNotSame(rewrite, boolQueryBuilder);
                 if (boolQueryBuilder.must().isEmpty() == false) {
-                    assertEquals(new TermsQueryBuilder("foo", "must"), rewrite.must().get(0));
+                    assertEquals(new TermsQueryBuilder(STRING_FIELD_NAME, "must"), rewrite.must().get(0));
                 }
                 if (boolQueryBuilder.should().isEmpty() == false) {
-                    assertEquals(new TermsQueryBuilder("foo", "should"), rewrite.should().get(0));
+                    assertEquals(new TermsQueryBuilder(STRING_FIELD_NAME, "should"), rewrite.should().get(0));
                 }
                 if (boolQueryBuilder.mustNot().isEmpty() == false) {
-                    assertEquals(new TermsQueryBuilder("foo", "must_not"), rewrite.mustNot().get(0));
+                    assertEquals(new TermsQueryBuilder(STRING_FIELD_NAME, "must_not"), rewrite.mustNot().get(0));
                 }
                 if (boolQueryBuilder.filter().isEmpty() == false) {
-                    assertEquals(new TermsQueryBuilder("foo", "filter"), rewrite.filter().get(0));
+                    assertEquals(new TermsQueryBuilder(STRING_FIELD_NAME, "filter"), rewrite.filter().get(0));
                 }
             } else {
                 assertSame(rewrite, boolQueryBuilder);
@@ -360,14 +361,14 @@ public void testRewriteWithMatchNone() throws IOException {
         assertEquals(new MatchNoneQueryBuilder(), rewritten);
 
         boolQueryBuilder = new BoolQueryBuilder();
-        boolQueryBuilder.must(new TermQueryBuilder("foo","bar"));
+        boolQueryBuilder.must(new TermQueryBuilder(STRING_FIELD_NAME,"bar"));
         boolQueryBuilder.filter(new WrapperQueryBuilder(new WrapperQueryBuilder(new MatchNoneQueryBuilder().toString()).toString()));
         rewritten = boolQueryBuilder.rewrite(createShardContext());
         assertEquals(new MatchNoneQueryBuilder(), rewritten);
 
         boolQueryBuilder = new BoolQueryBuilder();
-        boolQueryBuilder.must(new TermQueryBuilder("foo","bar"));
-        boolQueryBuilder.filter(new BoolQueryBuilder().should(new TermQueryBuilder("foo","bar"))
+        boolQueryBuilder.must(new TermQueryBuilder(STRING_FIELD_NAME,"bar"));
+        boolQueryBuilder.filter(new BoolQueryBuilder().should(new TermQueryBuilder(STRING_FIELD_NAME,"bar"))
             .filter(new MatchNoneQueryBuilder()));
         rewritten = Rewriteable.rewrite(boolQueryBuilder, createShardContext());
         assertEquals(new MatchNoneQueryBuilder(), rewritten);
@@ -378,7 +379,7 @@ public void testRewriteWithMatchNone() throws IOException {
         assertEquals(new MatchNoneQueryBuilder(), rewritten);
 
         boolQueryBuilder = new BoolQueryBuilder();
-        boolQueryBuilder.should(new TermQueryBuilder("foo", "bar"));
+        boolQueryBuilder.should(new TermQueryBuilder(STRING_FIELD_NAME, "bar"));
         boolQueryBuilder.should(new WrapperQueryBuilder(new MatchNoneQueryBuilder().toString()));
         rewritten = Rewriteable.rewrite(boolQueryBuilder, createShardContext());
         assertNotEquals(new MatchNoneQueryBuilder(), rewritten);
@@ -387,4 +388,16 @@ public void testRewriteWithMatchNone() throws IOException {
         rewritten = Rewriteable.rewrite(boolQueryBuilder, createShardContext());
         assertNotEquals(new MatchNoneQueryBuilder(), rewritten);
     }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        TermQueryBuilder termQuery = new TermQueryBuilder("unmapped_field", 42);
+        BoolQueryBuilder boolQuery = new BoolQueryBuilder();
+        boolQuery.must(termQuery);
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> boolQuery.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/BoostingQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/BoostingQueryBuilderTests.java
index 534126ee5f35a..22d4db861be82 100644
--- a/server/src/test/java/org/elasticsearch/index/query/BoostingQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/BoostingQueryBuilderTests.java
@@ -40,8 +40,8 @@ protected BoostingQueryBuilder doCreateTestQueryBuilder() {
 
     @Override
     protected void doAssertLuceneQuery(BoostingQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        Query positive = queryBuilder.positiveQuery().toQuery(context);
-        Query negative = queryBuilder.negativeQuery().toQuery(context);
+        Query positive = queryBuilder.positiveQuery().rewrite(context).toQuery(context);
+        Query negative = queryBuilder.negativeQuery().rewrite(context).toQuery(context);
         if (positive == null || negative == null) {
             assertThat(query, nullValue());
         } else {
@@ -103,4 +103,22 @@ public void testRewrite() throws IOException {
             assertEquals(new BoostingQueryBuilder(positive.rewrite(createShardContext()), negative.rewrite(createShardContext())), rewrite);
         }
     }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+
+        BoostingQueryBuilder queryBuilder1 = new BoostingQueryBuilder(
+                new TermQueryBuilder("unmapped_field", "foo"), new MatchNoneQueryBuilder());
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder1.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+
+        BoostingQueryBuilder queryBuilder2 = new BoostingQueryBuilder(
+                new MatchAllQueryBuilder(), new TermQueryBuilder("unmapped_field", "foo"));
+        e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder2.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/ConstantScoreQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/ConstantScoreQueryBuilderTests.java
index fd2ad04af5f95..5281788a8307d 100644
--- a/server/src/test/java/org/elasticsearch/index/query/ConstantScoreQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/ConstantScoreQueryBuilderTests.java
@@ -20,6 +20,7 @@
 package org.elasticsearch.index.query;
 
 import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.test.AbstractQueryTestCase;
@@ -41,9 +42,11 @@ protected ConstantScoreQueryBuilder doCreateTestQueryBuilder() {
 
     @Override
     protected void doAssertLuceneQuery(ConstantScoreQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        Query innerQuery = queryBuilder.innerQuery().toQuery(context);
+        Query innerQuery = queryBuilder.innerQuery().rewrite(context).toQuery(context);
         if (innerQuery == null) {
             assertThat(query, nullValue());
+        } else if (innerQuery instanceof MatchNoDocsQuery) {
+            assertThat(query, instanceOf(MatchNoDocsQuery.class));
         } else {
             assertThat(query, instanceOf(ConstantScoreQuery.class));
             ConstantScoreQuery constantScoreQuery = (ConstantScoreQuery) query;
@@ -107,4 +110,14 @@ public void testRewriteToMatchNone() throws IOException {
         QueryBuilder rewrite = constantScoreQueryBuilder.rewrite(createShardContext());
         assertEquals(rewrite, new MatchNoneQueryBuilder());
     }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        ConstantScoreQueryBuilder queryBuilder = new ConstantScoreQueryBuilder(new TermQueryBuilder("unmapped_field", "foo"));
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/IdsQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/IdsQueryBuilderTests.java
index 5f3ab8b108faf..95042d1deae24 100644
--- a/server/src/test/java/org/elasticsearch/index/query/IdsQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/IdsQueryBuilderTests.java
@@ -164,4 +164,14 @@ protected QueryBuilder parseQuery(XContentParser parser) throws IOException {
         }
         return query;
     }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContextWithNoType();
+        context.setAllowUnmappedFields(true);
+        IdsQueryBuilder queryBuilder = createTestQueryBuilder();
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java
index c6fd863a35ba4..9edd91fd812e0 100644
--- a/server/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java
@@ -57,8 +57,6 @@
 
 public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBuilder> {
 
-    boolean requiresRewrite = false;
-
     @Override
     protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
         mapperService.merge("_doc", new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef("_doc",
@@ -79,10 +77,6 @@ protected void initializeAdditionalMappings(MapperService mapperService) throws
     @Override
     protected NestedQueryBuilder doCreateTestQueryBuilder() {
         QueryBuilder innerQueryBuilder = RandomQueryBuilder.createQuery(random());
-        if (randomBoolean()) {
-            requiresRewrite = true;
-            innerQueryBuilder = new WrapperQueryBuilder(innerQueryBuilder.toString());
-        }
         NestedQueryBuilder nqb = new NestedQueryBuilder("nested1", innerQueryBuilder,
                 RandomPicks.randomFrom(random(), ScoreMode.values()));
         nqb.ignoreUnmapped(randomBoolean());
@@ -186,13 +180,14 @@ public void testFromJson() throws IOException {
 
     @Override
     public void testMustRewrite() throws IOException {
-        try {
-            super.testMustRewrite();
-        } catch (UnsupportedOperationException e) {
-            if (requiresRewrite == false) {
-                throw e;
-            }
-        }
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        TermQueryBuilder innerQueryBuilder = new TermQueryBuilder("nested1.unmapped_field", "foo");
+        NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("nested1", innerQueryBuilder,
+                RandomPicks.randomFrom(random(), ScoreMode.values()));
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> nestedQueryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
     }
 
     public void testIgnoreUnmapped() throws IOException {
diff --git a/server/src/test/java/org/elasticsearch/index/query/PrefixQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/PrefixQueryBuilderTests.java
index dba92d712c107..94596ffd6c58d 100644
--- a/server/src/test/java/org/elasticsearch/index/query/PrefixQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/PrefixQueryBuilderTests.java
@@ -20,11 +20,13 @@
 package org.elasticsearch.index.query;
 
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.test.AbstractQueryTestCase;
+import org.hamcrest.Matchers;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -68,12 +70,14 @@ private static PrefixQueryBuilder randomPrefixQuery() {
 
     @Override
     protected void doAssertLuceneQuery(PrefixQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        assertThat(query, instanceOf(PrefixQuery.class));
-        PrefixQuery prefixQuery = (PrefixQuery) query;
+        assertThat(query, Matchers.anyOf(instanceOf(PrefixQuery.class), instanceOf(MatchNoDocsQuery.class)));
+        if (context.fieldMapper(queryBuilder.fieldName()) != null) { // The field is mapped
+            PrefixQuery prefixQuery = (PrefixQuery) query;
 
-        String expectedFieldName = expectedFieldName(queryBuilder.fieldName());
-        assertThat(prefixQuery.getPrefix().field(), equalTo(expectedFieldName));
-        assertThat(prefixQuery.getPrefix().text(), equalTo(queryBuilder.value()));
+            String expectedFieldName = expectedFieldName(queryBuilder.fieldName());
+            assertThat(prefixQuery.getPrefix().field(), equalTo(expectedFieldName));
+            assertThat(prefixQuery.getPrefix().text(), equalTo(queryBuilder.value()));
+        }
     }
 
     public void testIllegalArguments() {
@@ -88,10 +92,10 @@ public void testIllegalArguments() {
 
     public void testBlendedRewriteMethod() throws IOException {
         String rewrite = "top_terms_blended_freqs_10";
-        Query parsedQuery = parseQuery(prefixQuery("field", "val").rewrite(rewrite)).toQuery(createShardContext());
+        Query parsedQuery = parseQuery(prefixQuery(STRING_FIELD_NAME, "val").rewrite(rewrite)).toQuery(createShardContext());
         assertThat(parsedQuery, instanceOf(PrefixQuery.class));
         PrefixQuery prefixQuery = (PrefixQuery) parsedQuery;
-        assertThat(prefixQuery.getPrefix(), equalTo(new Term("field", "val")));
+        assertThat(prefixQuery.getPrefix(), equalTo(new Term(STRING_FIELD_NAME, "val")));
         assertThat(prefixQuery.getRewriteMethod(), instanceOf(MultiTermQuery.TopTermsBlendedFreqScoringRewrite.class));
     }
 
@@ -153,7 +157,16 @@ public void testRewriteIndexQueryToNotMatchNone() throws Exception {
         PrefixQueryBuilder query = prefixQuery("_index", getIndex().getName());
         QueryShardContext queryShardContext = createShardContext();
         QueryBuilder rewritten = query.rewrite(queryShardContext);
-        assertThat(rewritten, instanceOf(PrefixQueryBuilder.class));
+        assertThat(rewritten, instanceOf(MatchAllQueryBuilder.class));
     }
 
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        PrefixQueryBuilder queryBuilder = new PrefixQueryBuilder("unmapped_field", "foo");
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java
index fea0356aa4cd0..a282bbe987d14 100644
--- a/server/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java
@@ -53,7 +53,6 @@
 import java.util.Map;
 
 import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
-import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.sameInstance;
@@ -245,16 +244,6 @@ public void testIllegalArguments() {
         expectThrows(IllegalArgumentException.class, () -> rangeQueryBuilder.format("badFormat"));
     }
 
-    /**
-     * Specifying a timezone together with an unmapped field should throw an exception.
-     */
-    public void testToQueryUnmappedWithTimezone() throws QueryShardException {
-        RangeQueryBuilder query = new RangeQueryBuilder("bogus_field");
-        query.from(1).to(10).timeZone("UTC");
-        QueryShardException e = expectThrows(QueryShardException.class, () -> query.toQuery(createShardContext()));
-        assertThat(e.getMessage(), containsString("[range] time_zone can not be applied"));
-    }
-
     public void testToQueryNumericField() throws IOException {
         Query parsedQuery = rangeQuery(INT_FIELD_NAME).from(23).to(54).includeLower(true).includeUpper(false).toQuery(createShardContext());
         // since age is automatically registered in data, we encode it as numeric
diff --git a/server/src/test/java/org/elasticsearch/index/query/ScriptScoreQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/ScriptScoreQueryBuilderTests.java
index 04322a01d0f68..d3ff1434c4d2c 100644
--- a/server/src/test/java/org/elasticsearch/index/query/ScriptScoreQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/ScriptScoreQueryBuilderTests.java
@@ -99,6 +99,19 @@ public void testCacheability() throws IOException {
         assertFalse("query should not be cacheable: " + queryBuilder.toString(), context.isCacheable());
     }
 
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        TermQueryBuilder termQueryBuilder = new TermQueryBuilder("unmapped_field", "foo");
+        String scriptStr = "1";
+        Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, scriptStr, Collections.emptyMap());
+        ScriptScoreQueryBuilder scriptScoreQueryBuilder = new ScriptScoreQueryBuilder(termQueryBuilder, script);
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> scriptScoreQueryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
+
     public void testDisallowExpensiveQueries() {
         QueryShardContext queryShardContext = mock(QueryShardContext.class);
         when(queryShardContext.allowExpensiveQueries()).thenReturn(false);
diff --git a/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java
index f3c80c2759c0f..69d096cf11ac2 100644
--- a/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java
@@ -90,7 +90,8 @@ protected void doAssertLuceneQuery(SpanMultiTermQueryBuilder queryBuilder, Query
         if (query instanceof SpanMatchNoDocsQuery) {
             return;
         }
-        assertThat(query, either(instanceOf(SpanMultiTermQueryWrapper.class)).or(instanceOf(FieldMaskingSpanQuery.class)));
+        assertThat(query, either(instanceOf(SpanMultiTermQueryWrapper.class))
+                .or(instanceOf(FieldMaskingSpanQuery.class)));
         if (query instanceof SpanMultiTermQueryWrapper) {
             SpanMultiTermQueryWrapper wrapper = (SpanMultiTermQueryWrapper) query;
             Query innerQuery = queryBuilder.innerQuery().toQuery(context);
diff --git a/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java
index 0bf6ddbc57438..376aa54ef15b3 100644
--- a/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java
@@ -21,11 +21,11 @@
 
 import com.fasterxml.jackson.core.io.JsonStringEncoder;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.PointRangeQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.common.ParsingException;
-import org.elasticsearch.common.lucene.BytesRefs;
 import org.elasticsearch.index.mapper.MappedFieldType;
 
 import java.io.IOException;
@@ -91,7 +91,7 @@ protected TermQueryBuilder createQueryBuilder(String fieldName, Object value) {
 
     @Override
     protected void doAssertLuceneQuery(TermQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class)));
+        assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class)).or(instanceOf(MatchNoDocsQuery.class)));
         MappedFieldType mapper = context.fieldMapper(queryBuilder.fieldName());
         if (query instanceof TermQuery) {
             TermQuery termQuery = (TermQuery) query;
@@ -99,14 +99,12 @@ protected void doAssertLuceneQuery(TermQueryBuilder queryBuilder, Query query, Q
             String expectedFieldName = expectedFieldName(queryBuilder.fieldName());
             assertThat(termQuery.getTerm().field(), equalTo(expectedFieldName));
 
-            if (mapper != null) {
-                Term term = ((TermQuery) mapper.termQuery(queryBuilder.value(), null)).getTerm();
-                assertThat(termQuery.getTerm(), equalTo(term));
-            } else {
-                assertThat(termQuery.getTerm().bytes(), equalTo(BytesRefs.toBytesRef(queryBuilder.value())));
-            }
-        } else {
+            Term term = ((TermQuery) mapper.termQuery(queryBuilder.value(), null)).getTerm();
+            assertThat(termQuery.getTerm(), equalTo(term));
+        } else if (mapper != null) {
             assertEquals(query, mapper.termQuery(queryBuilder.value(), null));
+        } else {
+            assertThat(query, instanceOf(MatchNoDocsQuery.class));
         }
     }
 
@@ -185,6 +183,16 @@ public void testRewriteIndexQueryToNotMatchNone() throws IOException {
         TermQueryBuilder query = QueryBuilders.termQuery("_index", getIndex().getName());
         QueryShardContext queryShardContext = createShardContext();
         QueryBuilder rewritten = query.rewrite(queryShardContext);
-        assertThat(rewritten, instanceOf(TermQueryBuilder.class));
-    }      
+        assertThat(rewritten, instanceOf(MatchAllQueryBuilder.class));
+    }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        TermQueryBuilder queryBuilder = new TermQueryBuilder("unmapped_field", "foo");
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java
index 4725961deceed..913934a766268 100644
--- a/server/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/TermsQueryBuilderTests.java
@@ -112,16 +112,13 @@ private TermsLookup randomTermsLookup() {
     protected void doAssertLuceneQuery(TermsQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
         if (queryBuilder.termsLookup() == null && (queryBuilder.values() == null || queryBuilder.values().isEmpty())) {
             assertThat(query, instanceOf(MatchNoDocsQuery.class));
-            MatchNoDocsQuery matchNoDocsQuery = (MatchNoDocsQuery) query;
-            assertThat(matchNoDocsQuery.toString(), containsString("No terms supplied for \"terms\" query."));
         } else if (queryBuilder.termsLookup() != null && randomTerms.size() == 0){
             assertThat(query, instanceOf(MatchNoDocsQuery.class));
-            MatchNoDocsQuery matchNoDocsQuery = (MatchNoDocsQuery) query;
-            assertThat(matchNoDocsQuery.toString(), containsString("No terms supplied for \"terms\" query."));
         } else {
             assertThat(query, either(instanceOf(TermInSetQuery.class))
                     .or(instanceOf(PointInSetQuery.class))
-                    .or(instanceOf(ConstantScoreQuery.class)));
+                    .or(instanceOf(ConstantScoreQuery.class))
+                    .or(instanceOf(MatchNoDocsQuery.class)));
             if (query instanceof ConstantScoreQuery) {
                 assertThat(((ConstantScoreQuery) query).getQuery(), instanceOf(BooleanQuery.class));
             }
@@ -141,8 +138,13 @@ protected void doAssertLuceneQuery(TermsQueryBuilder queryBuilder, Query query,
             }
 
             String fieldName = expectedFieldName(queryBuilder.fieldName());
-            TermInSetQuery expected = new TermInSetQuery(fieldName,
-                    terms.stream().filter(Objects::nonNull).map(Object::toString).map(BytesRef::new).collect(Collectors.toList()));
+            Query expected;
+            if (context.fieldMapper(fieldName) != null) {
+                expected = new TermInSetQuery(fieldName,
+                        terms.stream().filter(Objects::nonNull).map(Object::toString).map(BytesRef::new).collect(Collectors.toList()));
+            } else {
+                expected = new MatchNoDocsQuery();
+            }
             assertEquals(expected, query);
         }
     }
@@ -267,8 +269,16 @@ public void testMustRewrite() throws IOException {
         UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class,
                 () -> termsQueryBuilder.toQuery(createShardContext()));
         assertEquals("query must be rewritten first", e.getMessage());
-        assertEquals(rewriteAndFetch(termsQueryBuilder, createShardContext()), new TermsQueryBuilder(STRING_FIELD_NAME,
-            randomTerms.stream().filter(x -> x != null).collect(Collectors.toList()))); // terms lookup removes null values
+
+        // terms lookup removes null values
+        List<Object> nonNullTerms = randomTerms.stream().filter(x -> x != null).collect(Collectors.toList());
+        QueryBuilder expected;
+        if (nonNullTerms.isEmpty()) {
+            expected = new MatchNoneQueryBuilder();
+        } else {
+            expected = new TermsQueryBuilder(STRING_FIELD_NAME, nonNullTerms);
+        }
+        assertEquals(expected, rewriteAndFetch(termsQueryBuilder, createShardContext()));
     }
 
     public void testGeo() throws Exception {
@@ -329,7 +339,7 @@ public void testRewriteIndexQueryToNotMatchNone() throws IOException {
         TermsQueryBuilder query = new TermsQueryBuilder("_index", "does_not_exist", getIndex().getName());
         QueryShardContext queryShardContext = createShardContext();
         QueryBuilder rewritten = query.rewrite(queryShardContext);
-        assertThat(rewritten, instanceOf(TermsQueryBuilder.class));
+        assertThat(rewritten, instanceOf(MatchAllQueryBuilder.class));
     }      
     
     @Override
@@ -343,4 +353,5 @@ protected QueryBuilder parseQuery(XContentParser parser) throws IOException {
         }
         return query;
     }
+
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java
index bf88ab9ee2da6..9482e0bb87d49 100644
--- a/server/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java
@@ -152,6 +152,16 @@ public void testRewriteIndexQueryNotMatchNone() throws IOException {
         WildcardQueryBuilder query = new WildcardQueryBuilder("_index", firstHalfOfIndexName +"*");
         QueryShardContext queryShardContext = createShardContext();
         QueryBuilder rewritten = query.rewrite(queryShardContext);
-        assertThat(rewritten, instanceOf(WildcardQueryBuilder.class));
-    }      
+        assertThat(rewritten, instanceOf(MatchAllQueryBuilder.class));
+    }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        WildcardQueryBuilder queryBuilder = new WildcardQueryBuilder("unmapped_field", "foo");
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java
index d5ccff8402420..ad607a561fbce 100644
--- a/server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java
@@ -119,7 +119,7 @@ public void testFromJson() throws IOException {
 
     @Override
     public void testMustRewrite() throws IOException {
-        TermQueryBuilder tqb = new TermQueryBuilder("foo", "bar");
+        TermQueryBuilder tqb = new TermQueryBuilder(STRING_FIELD_NAME, "bar");
         WrapperQueryBuilder qb = new WrapperQueryBuilder(tqb.toString());
         UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, () -> qb.toQuery(createShardContext()));
         assertEquals("this query must be rewritten first", e.getMessage());
@@ -137,7 +137,7 @@ public void testRewriteWithInnerName() throws IOException {
     }
 
     public void testRewriteWithInnerBoost() throws IOException {
-        final TermQueryBuilder query = new TermQueryBuilder("foo", "bar").boost(2);
+        final TermQueryBuilder query = new TermQueryBuilder(STRING_FIELD_NAME, "bar").boost(2);
         QueryBuilder builder = new WrapperQueryBuilder(query.toString());
         QueryShardContext shardContext = createShardContext();
         assertEquals(query, builder.rewrite(shardContext));
@@ -149,15 +149,15 @@ public void testRewriteInnerQueryToo() throws IOException {
         QueryShardContext shardContext = createShardContext();
 
         QueryBuilder qb = new WrapperQueryBuilder(
-            new WrapperQueryBuilder(new TermQueryBuilder("foo", "bar").toString()).toString()
+            new WrapperQueryBuilder(new TermQueryBuilder(STRING_FIELD_NAME, "bar").toString()).toString()
         );
-        assertEquals(new TermQuery(new Term("foo", "bar")), qb.rewrite(shardContext).toQuery(shardContext));
+        assertEquals(new TermQuery(new Term(STRING_FIELD_NAME, "bar")), qb.rewrite(shardContext).toQuery(shardContext));
         qb = new WrapperQueryBuilder(
             new WrapperQueryBuilder(
-                new WrapperQueryBuilder(new TermQueryBuilder("foo", "bar").toString()).toString()
+                new WrapperQueryBuilder(new TermQueryBuilder(STRING_FIELD_NAME, "bar").toString()).toString()
             ).toString()
         );
-        assertEquals(new TermQuery(new Term("foo", "bar")), qb.rewrite(shardContext).toQuery(shardContext));
+        assertEquals(new TermQuery(new Term(STRING_FIELD_NAME, "bar")), qb.rewrite(shardContext).toQuery(shardContext));
 
         qb = new WrapperQueryBuilder(new BoolQueryBuilder().toString());
         assertEquals(new MatchAllDocsQuery(), qb.rewrite(shardContext).toQuery(shardContext));
diff --git a/server/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java
index fbaf93c0bd6f0..3f3e557e2ed4b 100644
--- a/server/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java
@@ -552,11 +552,12 @@ public void testMalformedThrowsException() throws IOException {
     }
 
     public void testCustomWeightFactorQueryBuilderWithFunctionScore() throws IOException {
-        Query parsedQuery = parseQuery(functionScoreQuery(termQuery("name.last", "banon"), weightFactorFunction(1.3f)))
-                .toQuery(createShardContext());
+        QueryShardContext context = createShardContext();
+        Query parsedQuery = parseQuery(functionScoreQuery(termQuery(STRING_FIELD_NAME_2, "banon"), weightFactorFunction(1.3f)))
+                .rewrite(context).toQuery(context);
         assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
         FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
-        assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term("name.last", "banon")));
+        assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "banon")));
         assertThat((double) (functionScoreQuery.getFunctions()[0]).getWeight(), closeTo(1.3, 0.001));
     }
 
@@ -642,14 +643,14 @@ public void testFromJson() throws IOException {
 
     public void testRewrite() throws IOException {
         FunctionScoreQueryBuilder functionScoreQueryBuilder =
-            new FunctionScoreQueryBuilder(new WrapperQueryBuilder(new TermQueryBuilder("foo", "bar").toString()))
+            new FunctionScoreQueryBuilder(new WrapperQueryBuilder(new TermQueryBuilder(STRING_FIELD_NAME, "bar").toString()))
                 .boostMode(CombineFunction.REPLACE)
                 .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                 .setMinScore(1)
                 .maxBoost(100);
         FunctionScoreQueryBuilder rewrite = (FunctionScoreQueryBuilder) functionScoreQueryBuilder.rewrite(createShardContext());
         assertNotSame(functionScoreQueryBuilder, rewrite);
-        assertEquals(rewrite.query(), new TermQueryBuilder("foo", "bar"));
+        assertEquals(rewrite.query(), new TermQueryBuilder(STRING_FIELD_NAME, "bar"));
         assertEquals(rewrite.boostMode(), CombineFunction.REPLACE);
         assertEquals(rewrite.scoreMode(), FunctionScoreQuery.ScoreMode.SUM);
         assertEquals(rewrite.getMinScore(), 1f, 0.0001);
@@ -657,18 +658,18 @@ public void testRewrite() throws IOException {
     }
 
     public void testRewriteWithFunction() throws IOException {
-        QueryBuilder firstFunction = new WrapperQueryBuilder(new TermQueryBuilder("tq", "1").toString());
-        TermQueryBuilder secondFunction = new TermQueryBuilder("tq", "2");
-        QueryBuilder queryBuilder = randomBoolean() ? new WrapperQueryBuilder(new TermQueryBuilder("foo", "bar").toString())
-                : new TermQueryBuilder("foo", "bar");
+        QueryBuilder firstFunction = new WrapperQueryBuilder(new TermQueryBuilder(STRING_FIELD_NAME_2, "1").toString());
+        TermQueryBuilder secondFunction = new TermQueryBuilder(STRING_FIELD_NAME_2, "2");
+        QueryBuilder queryBuilder = randomBoolean() ? new WrapperQueryBuilder(new TermQueryBuilder(STRING_FIELD_NAME, "bar").toString())
+                : new TermQueryBuilder(STRING_FIELD_NAME, "bar");
         FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(queryBuilder,
                 new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
                         new FunctionScoreQueryBuilder.FilterFunctionBuilder(firstFunction, new RandomScoreFunctionBuilder()),
                         new FunctionScoreQueryBuilder.FilterFunctionBuilder(secondFunction, new RandomScoreFunctionBuilder()) });
         FunctionScoreQueryBuilder rewrite = (FunctionScoreQueryBuilder) functionScoreQueryBuilder.rewrite(createShardContext());
         assertNotSame(functionScoreQueryBuilder, rewrite);
-        assertEquals(rewrite.query(), new TermQueryBuilder("foo", "bar"));
-        assertEquals(rewrite.filterFunctionBuilders()[0].getFilter(), new TermQueryBuilder("tq", "1"));
+        assertEquals(rewrite.query(), new TermQueryBuilder(STRING_FIELD_NAME, "bar"));
+        assertEquals(rewrite.filterFunctionBuilders()[0].getFilter(), new TermQueryBuilder(STRING_FIELD_NAME_2, "1"));
         assertSame(rewrite.filterFunctionBuilders()[1].getFilter(), secondFunction);
     }
 
@@ -685,7 +686,8 @@ public void testSingleScriptFunction() throws IOException {
             builder.boostMode(randomFrom(CombineFunction.values()));
         }
 
-        Query query = builder.toQuery(createShardContext());
+        QueryShardContext shardContext = createShardContext();
+        Query query = builder.rewrite(shardContext).toQuery(shardContext);
         assertThat(query, instanceOf(FunctionScoreQuery.class));
 
         CombineFunction expectedBoostMode = builder.boostMode() != null
@@ -840,4 +842,27 @@ private boolean isCacheable(FunctionScoreQueryBuilder queryBuilder) {
         }
         return true;
     }
+
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        TermQueryBuilder termQueryBuilder = new TermQueryBuilder("unmapped_field", "foo");
+
+        // main query needs rewriting
+        FunctionScoreQueryBuilder functionQueryBuilder1 = new FunctionScoreQueryBuilder(termQueryBuilder);
+        functionQueryBuilder1.setMinScore(1);
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> functionQueryBuilder1.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+
+        // filter needs rewriting
+        FunctionScoreQueryBuilder functionQueryBuilder2 = new FunctionScoreQueryBuilder(new MatchAllQueryBuilder(),
+                new FilterFunctionBuilder[] {
+                        new FilterFunctionBuilder(termQueryBuilder, new RandomScoreFunctionBuilder())
+                });
+        e = expectThrows(IllegalStateException.class,
+                () -> functionQueryBuilder2.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }
diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java
index 7ef271fbe5055..abaad56eedb5b 100644
--- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java
+++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java
@@ -683,13 +683,15 @@ private <T> void termsAggregator(ValueType valueType, MappedFieldType fieldType,
                     }
 
                     if (multiValued == false) {
+                        MappedFieldType filterFieldType = new KeywordFieldMapper.KeywordFieldType();
+                        filterFieldType.setName("include");
                         aggregationBuilder = new FilterAggregationBuilder("_name1", QueryBuilders.termQuery("include", "yes"));
                         aggregationBuilder.subAggregation(new TermsAggregationBuilder("_name2", valueType)
                             .executionHint(executionHint)
                             .size(numTerms)
                             .collectMode(randomFrom(Aggregator.SubAggCollectionMode.values()))
                             .field("field"));
-                        aggregator = createAggregator(aggregationBuilder, indexSearcher, fieldType);
+                        aggregator = createAggregator(aggregationBuilder, indexSearcher, fieldType, filterFieldType);
                         aggregator.preCollection();
                         indexSearcher.search(new MatchAllDocsQuery(), aggregator);
                         aggregator.postCollection();
diff --git a/server/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightBuilderTests.java b/server/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightBuilderTests.java
index d432573f38a9e..9daf41bf48df1 100644
--- a/server/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightBuilderTests.java
+++ b/server/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightBuilderTests.java
@@ -291,6 +291,7 @@ public MappedFieldType fieldMapper(String name) {
 
         for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
             HighlightBuilder highlightBuilder = randomHighlighterBuilder();
+            highlightBuilder = Rewriteable.rewrite(highlightBuilder, mockShardContext);
             SearchContextHighlight highlight = highlightBuilder.build(mockShardContext);
             for (SearchContextHighlight.Field field : highlight.fields()) {
                 String encoder = highlightBuilder.encoder() != null ? highlightBuilder.encoder() : HighlightBuilder.DEFAULT_ENCODER;
diff --git a/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java b/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java
index 931d06f07931f..332b3621eb392 100644
--- a/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java
+++ b/server/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java
@@ -48,6 +48,7 @@
 import org.elasticsearch.index.query.MatchAllQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.index.query.Rewriteable;
 import org.elasticsearch.index.query.TermQueryBuilder;
 import org.elasticsearch.script.MockScriptEngine;
 import org.elasticsearch.script.ScriptEngine;
@@ -154,7 +155,8 @@ public void testBuildSortField() throws IOException {
         QueryShardContext mockShardContext = createMockShardContext();
         for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
             T sortBuilder = createTestItem();
-            SortFieldAndFormat sortField = sortBuilder.build(mockShardContext);
+            SortFieldAndFormat sortField = Rewriteable.rewrite(sortBuilder, mockShardContext)
+                    .build(mockShardContext);
             sortFieldAssertions(sortBuilder, sortField.field, sortField.format);
         }
     }
diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java
index c8fe29b09b65c..ff5408a2ab808 100644
--- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java
+++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java
@@ -166,7 +166,9 @@ protected <A extends Aggregator> A createAggregator(Query query,
                                                         MappedFieldType... fieldTypes) throws IOException {
         SearchContext searchContext = createSearchContext(indexSearcher, indexSettings, query, bucketConsumer, fieldTypes);
         @SuppressWarnings("unchecked")
-        A aggregator = (A) aggregationBuilder.build(searchContext.getQueryShardContext(), null)
+        A aggregator = (A) aggregationBuilder
+            .rewrite(searchContext.getQueryShardContext())
+            .build(searchContext.getQueryShardContext(), null)
             .create(searchContext, null, true);
         return aggregator;
     }
diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java
index b27628e699d32..207709f6cc13a 100644
--- a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java
+++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java
@@ -22,6 +22,7 @@
 import com.fasterxml.jackson.core.io.JsonStringEncoder;
 
 import org.apache.lucene.search.BoostQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.spans.SpanBoostQuery;
@@ -453,7 +454,7 @@ public void testToQuery() throws IOException {
                         rewrite(secondLuceneQuery), rewrite(firstLuceneQuery));
             }
 
-            if (supportsBoost()) {
+            if (supportsBoost() && firstLuceneQuery instanceof MatchNoDocsQuery == false) {
                 secondQuery.boost(firstQuery.boost() + 1f + randomFloat());
                 Query thirdLuceneQuery = rewriteQuery(secondQuery, context).toQuery(context);
                 assertNotEquals("modifying the boost doesn't affect the corresponding lucene query", rewrite(firstLuceneQuery),
@@ -495,20 +496,23 @@ protected boolean supportsQueryName() {
      * {@link #doAssertLuceneQuery(AbstractQueryBuilder, Query, QueryShardContext)} for query specific checks.
      */
     private void assertLuceneQuery(QB queryBuilder, Query query, QueryShardContext context) throws IOException {
-        if (queryBuilder.queryName() != null) {
+        if (queryBuilder.queryName() != null && query instanceof MatchNoDocsQuery == false) {
             Query namedQuery = context.copyNamedQueries().get(queryBuilder.queryName());
             assertThat(namedQuery, equalTo(query));
         }
         if (query != null) {
             if (queryBuilder.boost() != AbstractQueryBuilder.DEFAULT_BOOST) {
-                assertThat(query, either(instanceOf(BoostQuery.class)).or(instanceOf(SpanBoostQuery.class)));
+                assertThat(query, either(instanceOf(BoostQuery.class)).or(instanceOf(SpanBoostQuery.class))
+                        .or(instanceOf(MatchNoDocsQuery.class)));
                 if (query instanceof SpanBoostQuery) {
                     SpanBoostQuery spanBoostQuery = (SpanBoostQuery) query;
                     assertThat(spanBoostQuery.getBoost(), equalTo(queryBuilder.boost()));
                     query = spanBoostQuery.getQuery();
-                } else {
+                } else if (query instanceof BoostQuery) {
                     BoostQuery boostQuery = (BoostQuery) query;
-                    assertThat(boostQuery.getBoost(), equalTo(queryBuilder.boost()));
+                    if (boostQuery.getQuery() instanceof MatchNoDocsQuery == false) {
+                        assertThat(boostQuery.getBoost(), equalTo(queryBuilder.boost()));
+                    }
                     query = boostQuery.getQuery();
                 }
             }
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java
index d859731b29ada..48ac7bea6a910 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java
@@ -29,6 +29,7 @@
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.BigArrays;
 import org.elasticsearch.index.IndexSettings;
+import org.elasticsearch.index.mapper.KeywordFieldMapper;
 import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryShardContext;
@@ -68,6 +69,7 @@
 
 public class DocumentSubsetBitsetCacheTests extends ESTestCase {
 
+    private static final String MISSING_FIELD_NAME = "does-not-exist";
     private static final int FIELD_COUNT = 10;
     private ExecutorService singleThreadExecutor;
 
@@ -99,7 +101,7 @@ public void testSameBitSetIsReturnedForIdenticalQuery() throws Exception {
     public void testNullBitSetIsReturnedForNonMatchingQuery() throws Exception {
         final DocumentSubsetBitsetCache cache = newCache(Settings.EMPTY);
         runTestOnIndex((shardContext, leafContext) -> {
-            final Query query = QueryBuilders.termQuery("does-not-exist", "any-value").toQuery(shardContext);
+            final Query query = QueryBuilders.termQuery(MISSING_FIELD_NAME, "any-value").rewrite(shardContext).toQuery(shardContext);
             final BitSet bitSet = cache.getBitSet(query, leafContext);
             assertThat(bitSet, nullValue());
         });
@@ -543,6 +545,17 @@ null, null, mapperService, null, null, xContentRegistry(), writableRegistry(),
 
     private void runTestOnIndices(int numberIndices, CheckedConsumer<List<TestIndexContext>, Exception> body) throws Exception {
         final MapperService mapperService = mock(MapperService.class);
+        when(mapperService.fieldType(Mockito.anyString())).thenAnswer(invocation -> {
+            final String fieldName = (String) invocation.getArguments()[0];
+            if (fieldName.equals(MISSING_FIELD_NAME)) {
+                return null;
+            } else {
+                KeywordFieldMapper.KeywordFieldType ft = new KeywordFieldMapper.KeywordFieldType();
+                ft.setName(fieldName);
+                ft.freeze();
+                return ft;
+            }
+        });
 
         final Client client = mock(Client.class);
         when(client.settings()).thenReturn(Settings.EMPTY);
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperIntegrationTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperIntegrationTests.java
index 117fa4b2f49c9..a66be9616600c 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperIntegrationTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperIntegrationTests.java
@@ -29,12 +29,14 @@
 import org.elasticsearch.common.util.BigArrays;
 import org.elasticsearch.common.util.concurrent.ThreadContext;
 import org.elasticsearch.index.IndexSettings;
+import org.elasticsearch.index.mapper.KeywordFieldMapper;
 import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.query.ParsedQuery;
 import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.index.query.TermsQueryBuilder;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.mock.orig.Mockito;
 import org.elasticsearch.script.ScriptService;
 import org.elasticsearch.search.internal.ContextIndexSearcher;
 import org.elasticsearch.test.AbstractBuilderTestCase;
@@ -69,6 +71,13 @@ public void testDLS() throws Exception {
         when(mapperService.documentMapper()).thenReturn(null);
         when(mapperService.simpleMatchToFullName(anyString()))
                 .then(invocationOnMock -> Collections.singletonList((String) invocationOnMock.getArguments()[0]));
+        when(mapperService.fieldType(Mockito.anyString())).then(invocation -> {
+            final String fieldName = (String) invocation.getArguments()[0];
+            KeywordFieldMapper.KeywordFieldType ft = new KeywordFieldMapper.KeywordFieldType();
+            ft.setName(fieldName);
+            ft.freeze();
+            return ft;
+        });
 
         final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
         final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext);
@@ -177,6 +186,13 @@ public void testDLSWithLimitedPermissions() throws Exception {
         when(mapperService.documentMapper()).thenReturn(null);
         when(mapperService.simpleMatchToFullName(anyString()))
                 .then(invocationOnMock -> Collections.singletonList((String) invocationOnMock.getArguments()[0]));
+        when(mapperService.fieldType(Mockito.anyString())).then(invocation -> {
+            final String fieldName = (String) invocation.getArguments()[0];
+            KeywordFieldMapper.KeywordFieldType ft = new KeywordFieldMapper.KeywordFieldType();
+            ft.setName(fieldName);
+            ft.freeze();
+            return ft;
+        });
 
         final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
         final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext);
@@ -203,7 +219,8 @@ public void testDLSWithLimitedPermissions() throws Exception {
         IndicesAccessControl.IndexAccessControl limitedIndexAccessControl = new IndicesAccessControl.IndexAccessControl(true, new
                 FieldPermissions(),
                 DocumentPermissions.filteredBy(queries));
-        IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(shardId.getIndex(), Settings.EMPTY);
+        IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(shardId.getIndex(),
+                Settings.builder().put(IndexSettings.ALLOW_UNMAPPED.getKey(), false).build());
         Client client = mock(Client.class);
         when(client.settings()).thenReturn(Settings.EMPTY);
         final long nowInMillis = randomNonNegativeLong();
diff --git a/x-pack/plugin/search-business-rules/src/test/java/org/elasticsearch/xpack/searchbusinessrules/PinnedQueryBuilderTests.java b/x-pack/plugin/search-business-rules/src/test/java/org/elasticsearch/xpack/searchbusinessrules/PinnedQueryBuilderTests.java
index db3d46fc1a7dd..e91ef3a5d76f9 100644
--- a/x-pack/plugin/search-business-rules/src/test/java/org/elasticsearch/xpack/searchbusinessrules/PinnedQueryBuilderTests.java
+++ b/x-pack/plugin/search-business-rules/src/test/java/org/elasticsearch/xpack/searchbusinessrules/PinnedQueryBuilderTests.java
@@ -170,4 +170,13 @@ public void testRewrite() throws IOException {
         assertThat(rewritten, instanceOf(PinnedQueryBuilder.class));
     }
 
+    @Override
+    public void testMustRewrite() throws IOException {
+        QueryShardContext context = createShardContext();
+        context.setAllowUnmappedFields(true);
+        PinnedQueryBuilder queryBuilder = new PinnedQueryBuilder(new TermQueryBuilder("unmapped_field", "42"));
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> queryBuilder.toQuery(context));
+        assertEquals("Rewrite first", e.getMessage());
+    }
 }