| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.solr.search; |
| |
| import java.io.File; |
| import java.util.Collections; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.solr.SolrTestCaseJ4; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.schema.IndexSchema; |
| import org.apache.solr.schema.ManagedIndexSchema; |
| import org.apache.solr.schema.SchemaField; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| |
| /** |
| * Requests to open a new searcher w/o any underlying change to the index exposed |
| * by the current searcher should result in the same searcher being re-used. |
| * |
| * Likewise, if there <em>is</em> in fact an underlying index change, we want to |
| * assert that a new searcher will in fact be opened. |
| */ |
| public class TestSearcherReuse extends SolrTestCaseJ4 { |
| |
| private static File solrHome; |
| |
| private static final String collection = "collection1"; |
| private static final String confPath = collection + "/conf"; |
| |
| /** |
| * We're using a Managed schema so we can confirm that opening a new searcher |
| * after a schema modification results in getting a new searcher with the new |
| * schema linked to it. |
| */ |
| @BeforeClass |
| private static void setupTempDirAndCoreWithManagedSchema() throws Exception { |
| solrHome = createTempDir().toFile(); |
| solrHome = solrHome.getAbsoluteFile(); |
| |
| File confDir = new File(solrHome, confPath); |
| File testHomeConfDir = new File(TEST_HOME(), confPath); |
| FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-managed-schema.xml"), confDir); |
| FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig.snippet.randomindexconfig.xml"), confDir); |
| FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-id-and-version-fields-only.xml"), confDir); |
| |
| // initCore will trigger an upgrade to managed schema, since the solrconfig has |
| // <schemaFactory class="ManagedIndexSchemaFactory" ... /> |
| System.setProperty("managed.schema.mutable", "true"); |
| initCore("solrconfig-managed-schema.xml", "schema-id-and-version-fields-only.xml", |
| solrHome.getPath()); |
| } |
| |
| @AfterClass |
| private static void afterClass() throws Exception { |
| solrHome = null; |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| super.tearDown(); |
| assertU(delQ("*:*")); |
| optimize(); |
| assertU(commit()); |
| } |
| |
| public void test() throws Exception { |
| |
| // seed some docs & segments |
| int numDocs = atLeast(1); |
| for (int i = 1; i <= numDocs; i++) { |
| // NOTE: starting at "1", we'll use id=0 later |
| assertU(adoc("id", ""+i)); |
| if (random().nextBoolean()) { |
| assertU(commit()); |
| } |
| } |
| |
| // with random merge policies, a regular commit can cause a segment to be flushed that can kick off a background merge |
| // that can cause a later commit to actually see changes and open a new searcher. This should not be possible with optimize |
| assertU(optimize()); |
| |
| // seed a single query into the cache |
| assertQ(req("*:*"), "//*[@numFound='"+numDocs+"']"); |
| |
| final SolrQueryRequest baseReq = req("q","foo"); |
| try { |
| // we make no index changes in this block, so the searcher should always be the same |
| // NOTE: we *have* to call getSearcher() in advance, it's a delayed binding |
| final SolrIndexSearcher expectedSearcher = getMainSearcher(baseReq); |
| |
| assertU(commit()); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| assertU(commit("openSearcher","true")); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| assertU(commit("softCommit","true")); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| assertU(commit("softCommit","true","openSearcher","true")); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| assertU(delQ("id:match_no_documents")); |
| assertU(commit("softCommit","true","openSearcher","true")); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| assertU(delI("0")); // no doc has this id, yet |
| assertU(commit("softCommit","true","openSearcher","true")); |
| assertSearcherHasNotChanged(expectedSearcher); |
| |
| } finally { |
| baseReq.close(); |
| } |
| |
| // now do a variety of things that *should* always guarantee a new searcher |
| SolrQueryRequest beforeReq; |
| |
| beforeReq = req("q","foo"); |
| try { |
| // NOTE: we *have* to call getSearcher() in advance: delayed binding |
| SolrIndexSearcher before = getMainSearcher(beforeReq); |
| |
| assertU(delI("1")); |
| assertU(commit()); |
| assertSearcherHasChanged(before); |
| } finally { |
| beforeReq.close(); |
| } |
| |
| beforeReq = req("q","foo"); |
| try { |
| // NOTE: we *have* to call getSearcher() in advance: delayed binding |
| SolrIndexSearcher before = getMainSearcher(beforeReq); |
| |
| assertU(adoc("id", "0")); |
| assertU(commit()); |
| assertSearcherHasChanged(before); |
| } finally { |
| beforeReq.close(); |
| } |
| |
| beforeReq = req("q","foo"); |
| try { |
| // NOTE: we *have* to call getSearcher() in advance: delayed binding |
| SolrIndexSearcher before = getMainSearcher(beforeReq); |
| |
| assertU(delQ("id:[0 TO 5]")); |
| assertU(commit()); |
| assertSearcherHasChanged(before); |
| } finally { |
| beforeReq.close(); |
| } |
| |
| beforeReq = req("q","foo"); |
| try { |
| // NOTE: we *have* to call getSearcher() in advance: delayed binding |
| SolrIndexSearcher before = getMainSearcher(beforeReq); |
| |
| // create a new field & add it. |
| assertTrue("schema not mutable", beforeReq.getSchema().isMutable()); |
| ManagedIndexSchema oldSchema = (ManagedIndexSchema) beforeReq.getSchema(); |
| SchemaField newField = oldSchema.newField |
| ("hoss", "string", Collections.<String,Object>emptyMap()); |
| IndexSchema newSchema = oldSchema.addField(newField); |
| h.getCore().setLatestSchema(newSchema); |
| |
| // sanity check, later asserts assume this |
| assertNotSame(oldSchema, newSchema); |
| |
| // the schema has changed - but nothing has requested a new Searcher yet |
| assertSearcherHasNotChanged(before); |
| |
| // only now should we get a new searcher... |
| assertU(commit("softCommit","true","openSearcher","true")); |
| assertSearcherHasChanged(before); |
| |
| // sanity that opening the new searcher was useful to get new schema... |
| SolrQueryRequest afterReq = req("q","foo"); |
| try { |
| assertSame(newSchema, afterReq.getSchema()); |
| assertSame(newSchema, getMainSearcher(afterReq).getSchema()); |
| } finally { |
| afterReq.close(); |
| } |
| |
| } finally { |
| beforeReq.close(); |
| } |
| |
| } |
| |
| /** |
| * Helper method to get the searcher from a request, and assert that it's the main searcher |
| */ |
| public static SolrIndexSearcher getMainSearcher(SolrQueryRequest req) { |
| SolrIndexSearcher s = req.getSearcher(); |
| assertMainSearcher(s); |
| return s; |
| } |
| |
| /** |
| * Sanity check that we didn't get a realtime (non-caching) searcher |
| */ |
| public static void assertMainSearcher(SolrIndexSearcher s) { |
| assertTrue("Searcher isn't 'main': " + s.toString(), |
| // TODO brittle, better solution? |
| s.toString().contains(" main{")); |
| assertTrue("Searcher is non-caching", s.isCachingEnabled()); |
| } |
| |
| /** |
| * Given an existing searcher, creates a new SolrRequest, and verifies that the |
| * searcher in that request is <b>not</b> the same as the previous searcher -- |
| * cleaningly closing the new SolrRequest either way. |
| */ |
| public static void assertSearcherHasChanged(SolrIndexSearcher previous) { |
| SolrQueryRequest req = req("*:*"); |
| try { |
| SolrIndexSearcher newSearcher = getMainSearcher(req); |
| assertNotSame(previous, newSearcher); |
| } finally { |
| req.close(); |
| } |
| } |
| |
| /** |
| * Given an existing searcher, creates a new SolrRequest, and verifies that the |
| * searcher in that request is the same as the expected searcher -- cleaningly |
| * closing the new SolrRequest either way. |
| */ |
| public static void assertSearcherHasNotChanged(SolrIndexSearcher expected) { |
| SolrQueryRequest req = req("*:*"); |
| try { |
| SolrIndexSearcher newSearcher = getMainSearcher(req); |
| assertSame(expected, newSearcher); |
| } finally { |
| req.close(); |
| } |
| } |
| |
| } |