This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 6fd6ad9 SOLR-15274: QueryElevationComponent, no data dir (#37)
6fd6ad9 is described below
commit 6fd6ad9286fc1aad34b01dbac2a82ea17f0d6b97
Author: David Smiley <[email protected]>
AuthorDate: Fri Apr 2 15:18:39 2021 -0400
SOLR-15274: QueryElevationComponent, no data dir (#37)
* Add auto config reloading after a commit. SolrCloud not supported.
* Remove support for the config file to be in a data dir. Obsolete.
* Remove support for the config file to have a suffix version.
---
solr/CHANGES.txt | 3 +
.../handler/component/QueryElevationComponent.java | 300 +++++++++++----------
.../solr/collection1/conf/solrconfig-elevate.xml | 13 -
.../DistributedQueryElevationComponentTest.java | 15 --
.../component/QueryElevationComponentTest.java | 25 +-
.../src/major-changes-in-solr-9.adoc | 5 +
.../src/the-query-elevation-component.adoc | 6 +-
7 files changed, 176 insertions(+), 191 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 132ff8b..a73487a 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -104,6 +104,9 @@ when told to. The admin UI now tells it to. (Nazerke
Seidan, David Smiley)
* SOLR-15276: V2 API call to look up async request status restful style of
"/cluster/command-status/1000" instead of
"/cluster/command-status?requestid=1000". (Eric Pugh)
+* SOLR-15274: The QueryElevationComponent now supports loading elevation file
changes on commits.
+ This doesn't work in SolrCloud (but may someday). QEC no longer supports a
config file in
+ the data dir. (David Smiley)
Other Changes
----------------------
diff --git
a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
index bbaa9a1..1eab71c 100644
---
a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
+++
b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
@@ -16,15 +16,11 @@
*/
package org.apache.solr.handler.component;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.invoke.MethodHandles;
+import java.lang.ref.WeakReference;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,9 +37,11 @@ import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
-import java.util.WeakHashMap;
import java.util.function.Consumer;
-
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
import com.carrotsearch.hppc.IntIntHashMap;
import com.carrotsearch.hppc.cursors.IntIntCursor;
import com.google.common.annotations.VisibleForTesting;
@@ -72,7 +70,6 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.QueryElevationParams;
import org.apache.solr.common.params.SolrParams;
@@ -81,6 +78,7 @@ import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrResourceNotFoundException;
import org.apache.solr.core.XmlConfigFile;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.transform.ElevatedMarkerFactory;
@@ -92,14 +90,11 @@ import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SortSpec;
import org.apache.solr.search.grouping.GroupingSpecification;
import org.apache.solr.util.RefCounted;
-import org.apache.solr.util.VersionedFile;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
/**
* A component to elevate some documents to the top of the result set.
@@ -132,22 +127,39 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
private static final boolean DEFAULT_SUBSET_MATCH = false;
private static final String DEFAULT_EXCLUDE_MARKER_FIELD_NAME = "excluded";
private static final String DEFAULT_EDITORIAL_MARKER_FIELD_NAME = "elevated";
+ private static final WeakReference<IndexReader> NULL_REF = new
WeakReference<>(null);
protected SolrParams initArgs;
+ protected String configFileName;
+
protected Analyzer queryAnalyzer;
protected SchemaField uniqueKeyField;
/** @see QueryElevationParams#FORCE_ELEVATION */
protected boolean forceElevation;
/** @see QueryElevationParams#USE_CONFIGURED_ELEVATED_ORDER */
protected boolean useConfiguredElevatedOrder;
-
+ /** If {@link #inform(SolrCore)} completed without error. */
protected boolean initialized;
+ private final Object LOCK = new Object(); // for cache*
+ /**
+ * Cached IndexReader associated with {@link #cacheElevationProvider}. Must
be accessed under
+ * lock.
+ */
+ private WeakReference<IndexReader> cacheIndexReader = NULL_REF;
+ /**
+ * Cached elevation provider. Must be accessed under lock. {@link
+ * #handleConfigLoadingException(Exception)} has the lock when called and
may access this if it
+ * wishes to return a previous good result.
+ */
+ protected ElevationProvider cacheElevationProvider = null; // keep null
/**
- * For each IndexReader, keep an ElevationProvider when the configuration is
loaded from the data directory.
- * The key is null if loaded from the config directory, and is never
re-loaded.
+ * Cached version / timestamp of the data underlying {@link
#cacheElevationProvider}. -1 means
+ * unsupported. Must be accessed under lock.
+ *
+ * @see #getConfigVersion(SolrCore)
*/
- private final Map<IndexReader, ElevationProvider> elevationProviderCache =
new WeakHashMap<>();
+ protected long cacheVersion;
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
@@ -218,66 +230,37 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
* @return The number of elevation rules parsed.
*/
protected int loadElevationConfiguration(SolrCore core) throws Exception {
- synchronized (elevationProviderCache) {
- elevationProviderCache.clear();
- String configFileName = initArgs.get(CONFIG_FILE);
+ synchronized (LOCK) {
+ clearElevationProviderCache();
+
+ this.configFileName = initArgs.get(CONFIG_FILE);
if (configFileName == null) {
// Throw an exception which is handled by
handleInitializationException().
// If not overridden handleInitializationException() simply skips this
exception.
throw new InitializationException("Missing component parameter " +
CONFIG_FILE + " - it has to define the path to the elevation configuration
file", InitializationExceptionCause.NO_CONFIG_FILE_DEFINED);
}
- boolean configFileExists = false;
- ElevationProvider elevationProvider = NO_OP_ELEVATION_PROVIDER;
-
- // check if using ZooKeeper
- ZkController zkController = core.getCoreContainer().getZkController();
- if (zkController != null) {
- // TODO : shouldn't have to keep reading the config name when it has
been read before
- configFileExists =
zkController.configFileExists(zkController.getZkStateReader().readConfigName(core.getCoreDescriptor().getCloudDescriptor().getCollectionName()),
configFileName);
- } else {
- File fC = new File(core.getResourceLoader().getConfigDir(),
configFileName);
- File fD = new File(core.getDataDir(), configFileName);
- if (fC.exists() == fD.exists()) {
- InitializationException e = new InitializationException("Missing
config file \"" + configFileName + "\" - either " + fC.getAbsolutePath() + " or
" + fD.getAbsolutePath() + " must exist, but not both",
InitializationExceptionCause.MISSING_CONFIG_FILE);
- elevationProvider = handleConfigLoadingException(e, true);
- elevationProviderCache.put(null, elevationProvider);
- } else if (fC.exists()) {
- if (fC.length() == 0) {
- InitializationException e = new InitializationException("Empty
config file \"" + configFileName + "\" - " + fC.getAbsolutePath(),
InitializationExceptionCause.EMPTY_CONFIG_FILE);
- elevationProvider = handleConfigLoadingException(e, true);
- } else {
- configFileExists = true;
- if (log.isInfoEnabled()) {
- log.info("Loading QueryElevation from: {}",
fC.getAbsolutePath());
- }
- XmlConfigFile cfg = new XmlConfigFile(core.getResourceLoader(),
configFileName);
- elevationProvider = loadElevationProvider(cfg);
- }
- elevationProviderCache.put(null, elevationProvider);
- }
- }
- //in other words, we think this is in the data dir, not the conf dir
- if (!configFileExists) {
- // preload the first data
- RefCounted<SolrIndexSearcher> searchHolder = null;
- try {
- searchHolder = core.getNewestSearcher(false);
- if (searchHolder == null) {
- elevationProvider = NO_OP_ELEVATION_PROVIDER;
- } else {
- IndexReader reader = searchHolder.get().getIndexReader();
- elevationProvider = getElevationProvider(reader, core);
- }
- } finally {
- if (searchHolder != null) searchHolder.decref();
+
+ // preload the first data
+ RefCounted<SolrIndexSearcher> searchHolder = null;
+ try {
+ searchHolder = core.getNewestSearcher(false);
+ if (searchHolder != null) {
+ IndexReader reader = searchHolder.get().getIndexReader();
+ getElevationProvider(reader, core); // computes and caches or throws
+ return cacheElevationProvider.size();
}
+ } finally {
+ if (searchHolder != null) searchHolder.decref();
}
- return elevationProvider.size();
+
+ assert false : "No Searcher; does this happen?"; // probably okay; lazy
load
+ return 0;
}
}
/**
- * Handles the exception that occurred while initializing this component.
+ * Handles the exception that occurred while initializing this component or
when
+ * parsing a new config file at runtime.
* If this method does not throw an exception, this component silently fails
to initialize
* and is muted with field {@link #initialized} which becomes {@code false}.
*/
@@ -290,93 +273,111 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
/**
* Handles an exception that occurred while loading the configuration
resource.
+ * The default implementation will return {@link #cacheElevationProvider} if
present, while
+ * also logging the error. If that is null (e.g. on startup) then the
exception is thrown.
+ * When re-throwing, wrap in a {@link SolrException}.
*
- * @param e The exception caught.
- * @param resourceAccessIssue <code>true</code> if the exception has been
thrown
- * because the resource could not be accessed
(missing or cannot be read)
- * or the config file is empty;
<code>false</code> if the resource has
- * been found and accessed but the error occurred
while loading the resource
- * (invalid format, incomplete or corrupted).
- * @return The {@link ElevationProvider} to use if the exception is
absorbed. If {@code null}
- * is returned, the {@link #NO_OP_ELEVATION_PROVIDER} is used but
not cached in
- * the {@link ElevationProvider} cache.
- * @throws E If the exception is not absorbed.
+ * @param e The exception caught. It will extend {@link IOException} if
there was a resource
+ * access issue.
+ * @return The {@link ElevationProvider} to use if the exception is absorbed
(vs re-thrown).
*/
- protected <E extends Exception> ElevationProvider
handleConfigLoadingException(E e, boolean resourceAccessIssue) throws E {
- throw e;
+ protected <E extends Exception> ElevationProvider
handleConfigLoadingException(E e) {
+ if (cacheElevationProvider != null) { // thus at runtime (a search is
in-progress)
+ String msg = e.toString(); // declare to avoid log isEnabled check
+ log.error(msg, e);
+ return cacheElevationProvider;
+ } else {
+ if (e instanceof SolrException) {
+ throw (SolrException) e;
+ } else {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ "Problem loading query elevation: " + e.toString(),
+ e);
+ }
+ }
}
/**
- * Gets the {@link ElevationProvider} from the data dir or from the cache.
+ * Gets the {@link ElevationProvider}; typically cached.
+ * If there was a problem, it might return a previously cached or dummy
entry, or possibly
+ * rethrow the exception.
*
* @return The cached or loaded {@link ElevationProvider}.
- * @throws java.io.IOException If the configuration
resource cannot be found, or if an I/O error occurs while analyzing the
triggering queries.
- * @throws org.xml.sax.SAXException If the configuration
resource is not a valid XML content.
- * @throws javax.xml.parsers.ParserConfigurationException If the
configuration resource is not a valid XML configuration.
- * @throws RuntimeException If the configuration resource is not
an XML content of the expected format
- * (either {@link RuntimeException} or
{@link org.apache.solr.common.SolrException}).
*/
@VisibleForTesting
- ElevationProvider getElevationProvider(IndexReader reader, SolrCore core)
throws Exception {
- synchronized (elevationProviderCache) {
- ElevationProvider elevationProvider;
- elevationProvider = elevationProviderCache.get(null);
- if (elevationProvider != null) return elevationProvider;
-
- elevationProvider = elevationProviderCache.get(reader);
- if (elevationProvider == null) {
- Exception loadingException = null;
- boolean resourceAccessIssue = false;
+ ElevationProvider getElevationProvider(IndexReader reader, SolrCore core) {
+ synchronized (LOCK) {
+ if (cacheElevationProvider != null && cacheIndexReader.get() == reader) {
+ return cacheElevationProvider; // cache hit !
+ }
+
+ final long version = getConfigVersion(core);
+ try {
+ // check version to see if should re-use
+ if (cacheVersion != -1 && cacheVersion == version) {
+ return cacheElevationProvider; // cache hit !
+ }
+
try {
- elevationProvider = loadElevationProvider(core);
- } catch (IOException e) {
- loadingException = e;
- resourceAccessIssue = true;
+ return cacheElevationProvider = loadElevationProvider(core);
} catch (Exception e) {
- loadingException = e;
+ return cacheElevationProvider = handleConfigLoadingException(e);
}
- boolean shouldCache = true;
- if (loadingException != null) {
- elevationProvider = handleConfigLoadingException(loadingException,
resourceAccessIssue);
- if (elevationProvider == null) {
- elevationProvider = NO_OP_ELEVATION_PROVIDER;
- shouldCache = false;
- }
- }
- if (shouldCache) {
- elevationProviderCache.put(reader, elevationProvider);
+ } finally {
+ if (cacheElevationProvider != null) { // could be null if re-throwing
+ cacheIndexReader = new WeakReference<>(reader); // cache the decision
+ cacheVersion = version;
}
}
- assert elevationProvider != null;
- return elevationProvider;
}
}
/**
- * Loads the {@link ElevationProvider} from the data dir.
- *
- * @return The loaded {@link ElevationProvider}.
- * @throws java.io.IOException If the configuration
resource cannot be found, or if an I/O error occurs while analyzing the
triggering queries.
- * @throws org.xml.sax.SAXException If the configuration
resource is not a valid XML content.
- * @throws javax.xml.parsers.ParserConfigurationException If the
configuration resource is not a valid XML configuration.
- * @throws RuntimeException If the configuration resource is not
an XML content of the expected format
- * (either {@link RuntimeException} or
{@link org.apache.solr.common.SolrException}).
+ * Returns a version number for the config file, or -1 if not supported. It
can be a timestamp; it
+ * need not strictly increase.
*/
- private ElevationProvider loadElevationProvider(SolrCore core) throws
IOException, SAXException, ParserConfigurationException {
- String configFileName = initArgs.get(CONFIG_FILE);
- if (configFileName == null) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
- "QueryElevationComponent must specify argument: " + CONFIG_FILE);
+ protected long getConfigVersion(SolrCore core) {
+ // TODO move this mechanism to a SolrResourceLoader.getVersion /
getLastModTime
+ try {
+ String configDir = core.getResourceLoader().getConfigDir(); //
unsupported in ZK
+ Path cfg = Path.of(configDir).resolve(configFileName);
+ return Files.getLastModifiedTime(cfg).toMillis();
+ } catch (Exception ignore) {
}
- log.info("Loading QueryElevation from data dir: {}", configFileName);
+ return -1; // don't know (e.g. Zookeeper as of this writing)
+ }
+ /**
+ * Loads the {@link ElevationProvider}.
+ *
+ * @return The loaded {@link ElevationProvider}; not null.
+ */
+ private ElevationProvider loadElevationProvider(SolrCore core) throws
Exception {
XmlConfigFile cfg;
- ZkController zkController = core.getCoreContainer().getZkController();
- if (zkController != null) {
- cfg = new XmlConfigFile(core.getResourceLoader(), configFileName, null,
null);
- } else {
- InputStream is = VersionedFile.getLatestFile(core.getDataDir(),
configFileName);
- cfg = new XmlConfigFile(core.getResourceLoader(), configFileName, new
InputSource(is), null);
+ try {
+ cfg = new XmlConfigFile(core.getResourceLoader(), configFileName);
+ } catch (SolrResourceNotFoundException e) {
+ String msg = "Missing config file \"" + configFileName + "\"";
+ if (Files.exists(Path.of(core.getDataDir(), configFileName))) {
+ msg += ". Found it in the data dir but this is no longer supported
since 9.0.";
+ }
+ throw new InitializationException(msg,
InitializationExceptionCause.MISSING_CONFIG_FILE);
+ } catch (Exception e) {
+ // See if it's because the file is empty; wrap it if so.
+ boolean isEmpty = false;
+ try (var input = core.getResourceLoader().openResource(configFileName)) {
+ if (input.read() == -1) { // thus empty file
+ isEmpty = true;
+ }
+ } catch (Exception ignored) {
+ }
+ if (isEmpty) {
+ throw new InitializationException(
+ "Empty config file \"" + configFileName + "\"",
+ InitializationExceptionCause.EMPTY_CONFIG_FILE);
+ }
+ throw e;
}
ElevationProvider elevationProvider = loadElevationProvider(cfg);
assert elevationProvider != null;
@@ -384,7 +385,7 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
}
/**
- * Loads the {@link ElevationProvider}.
+ * Loads the {@link ElevationProvider}. Not null.
*
* @throws RuntimeException If the config does not provide an XML content of
the expected format
* (either {@link RuntimeException} or {@link
org.apache.solr.common.SolrException}).
@@ -485,17 +486,13 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
SolrParams params = rb.req.getParams();
String paramElevatedIds = params.get(QueryElevationParams.IDS);
String paramExcludedIds = params.get(QueryElevationParams.EXCLUDE);
- try {
- if (paramElevatedIds != null || paramExcludedIds != null) {
- List<String> elevatedIds = paramElevatedIds != null ?
StrUtils.splitSmart(paramElevatedIds,",", true) : Collections.emptyList();
- List<String> excludedIds = paramExcludedIds != null ?
StrUtils.splitSmart(paramExcludedIds, ",", true) : Collections.emptyList();
- return new
ElevationBuilder().addElevatedIds(elevatedIds).addExcludedIds(excludedIds).build();
- } else {
- IndexReader reader = rb.req.getSearcher().getIndexReader();
- return getElevationProvider(reader,
rb.req.getCore()).getElevationForQuery(queryString);
- }
- } catch (Exception e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error
loading elevation", e);
+ if (paramElevatedIds != null || paramExcludedIds != null) {
+ List<String> elevatedIds = paramElevatedIds != null ?
StrUtils.splitSmart(paramElevatedIds,",", true) : Collections.emptyList();
+ List<String> excludedIds = paramExcludedIds != null ?
StrUtils.splitSmart(paramExcludedIds, ",", true) : Collections.emptyList();
+ return new
ElevationBuilder().addElevatedIds(elevatedIds).addExcludedIds(excludedIds).build();
+ } else {
+ IndexReader reader = rb.req.getSearcher().getIndexReader();
+ return getElevationProvider(reader,
rb.req.getCore()).getElevationForQuery(queryString);
}
}
@@ -753,15 +750,19 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
elevationBuilder.addElevatedIds(elevatedIds == null ?
Collections.emptyList() : Arrays.asList(elevatedIds));
elevationBuilder.addExcludedIds(excludedIds == null ?
Collections.emptyList() : Arrays.asList(excludedIds));
Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap =
ImmutableMap.of(elevatingQuery, elevationBuilder);
- synchronized (elevationProviderCache) {
- elevationProviderCache.computeIfAbsent(reader, k ->
createElevationProvider(elevationBuilderMap));
+ synchronized (LOCK) {
+ cacheIndexReader = new WeakReference<>(reader);
+ cacheElevationProvider = createElevationProvider(elevationBuilderMap);
+ cacheVersion = -1;
}
}
@VisibleForTesting
void clearElevationProviderCache() {
- synchronized (elevationProviderCache) {
- elevationProviderCache.clear();
+ synchronized (LOCK) {
+ cacheIndexReader = NULL_REF;
+ cacheElevationProvider = null;
+ cacheVersion = -1;
}
}
@@ -769,16 +770,17 @@ public class QueryElevationComponent extends
SearchComponent implements SolrCore
// Exception
//---------------------------------------------------------------------------------
- private static class InitializationException extends Exception {
+ private static class InitializationException extends SolrException {
private final InitializationExceptionCause exceptionCause;
InitializationException(String message, InitializationExceptionCause
exceptionCause) {
- super(message);
+ super(ErrorCode.SERVER_ERROR, message);
this.exceptionCause = exceptionCause;
}
}
+ /** @see #handleInitializationException(Exception,
InitializationExceptionCause) */
protected enum InitializationExceptionCause {
/**
* The component parameter {@link #FIELD_TYPE} defines an unknown field
type.
diff --git
a/solr/core/src/test-files/solr/collection1/conf/solrconfig-elevate.xml
b/solr/core/src/test-files/solr/collection1/conf/solrconfig-elevate.xml
index 75d36c9..4157dc5 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-elevate.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-elevate.xml
@@ -95,10 +95,6 @@
<str name="queryFieldType">string</str>
<str name="config-file">foo.xml</str>
</searchComponent>-->
- <searchComponent name="dataElevate"
class="org.apache.solr.handler.component.QueryElevationComponent" >
- <str name="queryFieldType">string</str>
- <str name="config-file">${elevate.data.file:elevate-data.xml}</str>
- </searchComponent>
<requestHandler name="/elevate"
class="org.apache.solr.handler.component.SearchHandler">
<lst name="defaults">
@@ -109,15 +105,6 @@
</arr>
</requestHandler>
- <requestHandler name="/dataElevate"
class="org.apache.solr.handler.component.SearchHandler">
- <lst name="defaults">
- <str name="echoParams">explicit</str>
- </lst>
- <arr name="last-components">
- <str>dataElevate</str>
- </arr>
- </requestHandler>
-
<!--<requestHandler name="/badElevate"
class="org.apache.solr.handler.component.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
diff --git
a/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
b/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
index 39df923..42fcb95 100644
---
a/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
@@ -16,10 +16,7 @@
*/
package org.apache.solr.handler.component;
-import java.io.File;
-
import org.apache.lucene.util.Constants;
-
import org.apache.solr.BaseDistributedSearchTestCase;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
@@ -28,7 +25,6 @@ import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
-import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -52,17 +48,6 @@ public class DistributedQueryElevationComponentTest extends
BaseDistributedSearc
schemaString = "schema11.xml";
}
- @BeforeClass
- public static void beforeClass() {
- System.setProperty("elevate.data.file", "elevate.xml");
- File parent = new File(TEST_HOME(), "conf");
- }
-
- @AfterClass
- public static void afterClass() {
- System.clearProperty("elevate.data.file");
- }
-
@Test
@ShardsFixed(num = 3)
public void test() throws Exception {
diff --git
a/solr/core/src/test/org/apache/solr/handler/component/QueryElevationComponentTest.java
b/solr/core/src/test/org/apache/solr/handler/component/QueryElevationComponentTest.java
index 4db3825..d236883 100644
---
a/solr/core/src/test/org/apache/solr/handler/component/QueryElevationComponentTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/component/QueryElevationComponentTest.java
@@ -22,6 +22,7 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@@ -39,7 +40,6 @@ import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.CollapsingQParserPlugin;
import org.apache.solr.search.SolrIndexSearcher;
-import org.apache.solr.util.FileUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -89,13 +89,6 @@ public class QueryElevationComponentTest extends
SolrTestCaseJ4 {
}
private void init(String config, String schema) throws Exception {
- //write out elevate-data.xml to the Data dir first by copying it from
conf, which we know exists, this way we can test both conf and data
configurations
- File parent = new File(TEST_HOME() + "/collection1", "conf");
- File elevateFile = new File(parent, "elevate.xml");
- File elevateDataFile = new File(initAndGetDataDir(), "elevate-data.xml");
- FileUtils.copyFile(elevateFile, elevateDataFile);
-
-
initCore(config,schema);
clearIndex();
assertU(commit());
@@ -714,15 +707,21 @@ public class QueryElevationComponentTest extends
SolrTestCaseJ4 {
@Test
public void testElevationReloading() throws Exception {
+ // need a mutable solr home. Copying all collection1 is a lot but this is
only one test.
+ final Path solrHome = createTempDir();
+ copyMinConf(solrHome.resolve("collection1").toFile(), null,
"solrconfig-elevate.xml");
+
+ File configFile =
+
solrHome.resolve("collection1").resolve("conf").resolve("elevate.xml").toFile();
+ writeElevationConfigFile(configFile, "aaa", "A");
+
+ initCore("solrconfig.xml", "schema.xml", solrHome.toString());
+
try {
- init("schema12.xml");
- String testfile = "data-elevation.xml";
- File configFile = new File(h.getCore().getDataDir(), testfile);
- writeElevationConfigFile(configFile, "aaa", "A");
QueryElevationComponent comp = (QueryElevationComponent)
h.getCore().getSearchComponent("elevate");
NamedList<String> args = new NamedList<>();
- args.add(QueryElevationComponent.CONFIG_FILE, testfile);
+ args.add(QueryElevationComponent.CONFIG_FILE, configFile.getName());
comp.init(args);
comp.inform(h.getCore());
diff --git a/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
index c52bc47..36f4bf6 100644
--- a/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
+++ b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
@@ -166,6 +166,11 @@ Upgrade is to be done using Collection API
MIGRATESTATEFORMAT action using a pre
See for example
https://solr.apache.org/guide/8_8/cluster-node-management.html#migratestateforma[Solr
8.8 Ref Guide].
// Can't link directly to .adoc file, need to link to 8.something ref guide as
MIGRATESTATEFORMAT no longer exists in 9.0.
+* If you're using Solr in standalone mode with the
<<the-query-elevation-component.adoc#,Query Elevation Component>> with it's
elevation file in the data directory, you'll have to move it to the
<<config-sets.adoc#,Configset>> instead.
+The only reason QEC supported the data directory was to support loading its
changes on a commit instead of a more expensive core reload.
+That feature now works from the configset dir too.
+SolrCloud doesn't support that but may sometime.
+
=== Rolling Upgrades with Solr 9
=== Reindexing After Upgrades in Solr 9
diff --git a/solr/solr-ref-guide/src/the-query-elevation-component.adoc
b/solr/solr-ref-guide/src/the-query-elevation-component.adoc
index 75b187b..50e7109 100644
--- a/solr/solr-ref-guide/src/the-query-elevation-component.adoc
+++ b/solr/solr-ref-guide/src/the-query-elevation-component.adoc
@@ -81,7 +81,11 @@ Here is the corresponding example of field type
(traditionally in `managed-schem
For example, to unescape only non-alphanumeric, the pattern could be
`\\([^\p\{IsAlphabetic}\p\{Digit}])`.
`config-file`::
-Path to the file that defines query elevation. This file must exist in
`<instanceDir>/conf/<config-file>` or `<dataDir>/<config-file>`. If the file
exists in the `conf/` directory it will be loaded once at startup. If it exists
in the `data/` directory, it will be reloaded for each IndexReader.
+Path to the file that defines the elevation rules.
+This file must exist in the <<config-sets.adoc#,Configset>>.
+Unlike most configuration, this component will re-read its configuration if
the file changed following a commit.
+However, that doesn't work in SolrCloud, and there has to be an actual index
change for a commit to have an effect for it to be used as a way to pick up
changes.
+In all cases, you can reload affected cores/collections to use any new
configuration in a configset.
`forceElevation`::
By default, this component respects the requested `sort` parameter: if the
request asks to sort by date, it will order the results by date. If
`forceElevation=true`, results will first return the boosted docs, then order
by date. This defaults to `false`.