/* * 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(); } } }