Plugins: one single (global) way to register custom query parsers

There are different ways to register custom query parsers through plugins, a couple of them work per index via index settings, which is probably even too flexible. There also three different ways to add a global custom query parser through either IndicesQueriesModule or IndicesQueriesRegistry. This commit consolidates the registration of custom query parsers via IndicesQueriesModule#addQuery(Class<? extends QueryParser>). The complexity of supporting parsers per index is not needed hence it got removed. Also the other ways of registering global custom parsers are dropped in favour of the one mentioned above.

Closes #11481
This commit is contained in:
javanna 2015-06-03 10:45:29 +02:00 committed by Luca Cavanna
parent f336cea35e
commit 2ef0fcfd6a
16 changed files with 257 additions and 742 deletions

View File

@ -1,134 +0,0 @@
/*
* 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.query;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Scopes;
import org.elasticsearch.common.inject.assistedinject.FactoryProvider;
import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
import java.util.LinkedList;
import java.util.Map;
/**
*
*/
public class IndexQueryParserModule extends AbstractModule {
/**
* A custom processor that can be extended to process and bind custom implementations of
* {@link QueryParserFactory}, and {@link FilterParser}.
*/
public static class QueryParsersProcessor {
/**
* Extension point to bind a custom {@link QueryParserFactory}.
*/
public void processXContentQueryParsers(XContentQueryParsersBindings bindings) {
}
public static class XContentQueryParsersBindings {
private final MapBinder<String, QueryParserFactory> binder;
private final Map<String, Settings> groupSettings;
public XContentQueryParsersBindings(MapBinder<String, QueryParserFactory> binder, Map<String, Settings> groupSettings) {
this.binder = binder;
this.groupSettings = groupSettings;
}
public MapBinder<String, QueryParserFactory> binder() {
return binder;
}
public Map<String, Settings> groupSettings() {
return groupSettings;
}
public void processXContentQueryParser(String name, Class<? extends QueryParser> xcontentQueryParser) {
if (!groupSettings.containsKey(name)) {
binder.addBinding(name).toProvider(FactoryProvider.newFactory(QueryParserFactory.class, xcontentQueryParser)).in(Scopes.SINGLETON);
}
}
}
}
private final Settings settings;
private final LinkedList<QueryParsersProcessor> processors = Lists.newLinkedList();
private final Map<String, Class<? extends QueryParser>> queries = Maps.newHashMap();
public IndexQueryParserModule(Settings settings) {
this.settings = settings;
}
/**
* Adds a custom query parser.
*
* @param name The name of the query parser
* @param queryParser the class of the query parser
*/
public void addQueryParser(String name, Class<? extends QueryParser> queryParser) {
queries.put(name, queryParser);
}
public IndexQueryParserModule addProcessor(QueryParsersProcessor processor) {
processors.addFirst(processor);
return this;
}
@Override
protected void configure() {
bind(IndexQueryParserService.class).asEagerSingleton();
bind(InnerHitsQueryParserHelper.class).asEagerSingleton();
// handle XContenQueryParsers
MapBinder<String, QueryParserFactory> queryBinder
= MapBinder.newMapBinder(binder(), String.class, QueryParserFactory.class);
Map<String, Settings> xContentQueryParserGroups = settings.getGroups(IndexQueryParserService.Defaults.QUERY_PREFIX);
for (Map.Entry<String, Settings> entry : xContentQueryParserGroups.entrySet()) {
String qName = entry.getKey();
Settings qSettings = entry.getValue();
Class<? extends QueryParser> type = qSettings.getAsClass("type", null);
if (type == null) {
throw new IllegalArgumentException("Query Parser [" + qName + "] must be provided with a type");
}
queryBinder.addBinding(qName).toProvider(FactoryProvider.newFactory(QueryParserFactory.class,
qSettings.getAsClass("type", null))).in(Scopes.SINGLETON);
}
QueryParsersProcessor.XContentQueryParsersBindings xContentQueryParsersBindings = new QueryParsersProcessor.XContentQueryParsersBindings(queryBinder, xContentQueryParserGroups);
for (QueryParsersProcessor processor : processors) {
processor.processXContentQueryParsers(xContentQueryParsersBindings);
}
for (Map.Entry<String, Class<? extends QueryParser>> entry : queries.entrySet()) {
queryBinder.addBinding(entry.getKey()).toProvider(FactoryProvider.newFactory(QueryParserFactory.class, entry.getValue())).in(Scopes.SINGLETON);
}
}
}

View File

@ -19,8 +19,6 @@
package org.elasticsearch.index.query;
import com.google.common.collect.ImmutableMap;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.CloseableThreadLocal;
import org.elasticsearch.Version;
@ -48,23 +46,12 @@ import org.elasticsearch.script.ScriptService;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
/**
*
*/
public class IndexQueryParserService extends AbstractIndexComponent {
public static final class Defaults {
public static final String QUERY_PREFIX = "index.queryparser.query";
public static final String FILTER_PREFIX = "index.queryparser.filter";
}
public static final String DEFAULT_FIELD = "index.query.default_field";
public static final String QUERY_STRING_LENIENT = "index.query_string.lenient";
public static final String PARSE_STRICT = "index.query.parse.strict";
@ -91,7 +78,7 @@ public class IndexQueryParserService extends AbstractIndexComponent {
final BitsetFilterCache bitsetFilterCache;
private final Map<String, QueryParser> queryParsers;
private final IndicesQueriesRegistry indicesQueriesRegistry;
private String defaultField;
private boolean queryStringLenient;
@ -104,8 +91,7 @@ public class IndexQueryParserService extends AbstractIndexComponent {
ScriptService scriptService, AnalysisService analysisService,
MapperService mapperService, IndexCache indexCache, IndexFieldDataService fieldDataService,
BitsetFilterCache bitsetFilterCache,
@Nullable SimilarityService similarityService,
@Nullable Map<String, QueryParserFactory> namedQueryParsers) {
@Nullable SimilarityService similarityService) {
super(index, indexSettings);
this.scriptService = scriptService;
this.analysisService = analysisService;
@ -119,29 +105,7 @@ public class IndexQueryParserService extends AbstractIndexComponent {
this.queryStringLenient = indexSettings.getAsBoolean(QUERY_STRING_LENIENT, false);
this.strict = indexSettings.getAsBoolean(PARSE_STRICT, false);
this.defaultAllowUnmappedFields = indexSettings.getAsBoolean(ALLOW_UNMAPPED, true);
List<QueryParser> queryParsers = newArrayList();
if (namedQueryParsers != null) {
Map<String, Settings> queryParserGroups = indexSettings.getGroups(IndexQueryParserService.Defaults.QUERY_PREFIX);
for (Map.Entry<String, QueryParserFactory> entry : namedQueryParsers.entrySet()) {
String queryParserName = entry.getKey();
QueryParserFactory queryParserFactory = entry.getValue();
Settings queryParserSettings = queryParserGroups.get(queryParserName);
if (queryParserSettings == null) {
queryParserSettings = EMPTY_SETTINGS;
}
queryParsers.add(queryParserFactory.create(queryParserName, queryParserSettings));
}
}
Map<String, QueryParser> queryParsersMap = newHashMap();
queryParsersMap.putAll(indicesQueriesRegistry.queryParsers());
if (queryParsers != null) {
for (QueryParser queryParser : queryParsers) {
add(queryParsersMap, queryParser);
}
}
this.queryParsers = ImmutableMap.copyOf(queryParsersMap);
this.indicesQueriesRegistry = indicesQueriesRegistry;
}
public void close() {
@ -157,7 +121,7 @@ public class IndexQueryParserService extends AbstractIndexComponent {
}
public QueryParser queryParser(String name) {
return queryParsers.get(name);
return indicesQueriesRegistry.queryParsers().get(name);
}
public ParsedQuery parse(QueryBuilder queryBuilder) {
@ -349,10 +313,4 @@ public class IndexQueryParserService extends AbstractIndexComponent {
parseContext.reset(null);
}
}
private void add(Map<String, QueryParser> map, QueryParser queryParser) {
for (String name : queryParser.names()) {
map.put(name.intern(), queryParser);
}
}
}

View File

@ -1,30 +0,0 @@
/*
* 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.query;
import org.elasticsearch.common.settings.Settings;
/**
*
*/
public interface QueryParserFactory {
QueryParser create(String name, Settings settings);
}

View File

@ -57,7 +57,6 @@ import org.elasticsearch.index.indexing.IndexingStats;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MapperServiceModule;
import org.elasticsearch.index.merge.MergeStats;
import org.elasticsearch.index.query.IndexQueryParserModule;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.recovery.RecoveryStats;
import org.elasticsearch.index.refresh.RefreshStats;
@ -315,7 +314,6 @@ public class IndicesService extends AbstractLifecycleComponent<IndicesService> i
modules.add(new IndexCacheModule(indexSettings));
modules.add(new IndexFieldDataModule(indexSettings));
modules.add(new MapperServiceModule());
modules.add(new IndexQueryParserModule(indexSettings));
modules.add(new IndexAliasesServiceModule());
modules.add(new IndexModule(indexSettings));

View File

@ -30,30 +30,21 @@ import java.util.Set;
public class IndicesQueriesModule extends AbstractModule {
private Set<Class<QueryParser>> queryParsersClasses = Sets.newHashSet();
private Set<QueryParser> queryParsers = Sets.newHashSet();
private Set<Class<? extends QueryParser>> queryParsersClasses = Sets.newHashSet();
public synchronized IndicesQueriesModule addQuery(Class<QueryParser> queryParser) {
public synchronized IndicesQueriesModule addQuery(Class<? extends QueryParser> queryParser) {
queryParsersClasses.add(queryParser);
return this;
}
public synchronized IndicesQueriesModule addQuery(QueryParser queryParser) {
queryParsers.add(queryParser);
return this;
}
@Override
protected void configure() {
bind(IndicesQueriesRegistry.class).asEagerSingleton();
Multibinder<QueryParser> qpBinders = Multibinder.newSetBinder(binder(), QueryParser.class);
for (Class<QueryParser> queryParser : queryParsersClasses) {
for (Class<? extends QueryParser> queryParser : queryParsersClasses) {
qpBinders.addBinding().to(queryParser).asEagerSingleton();
}
for (QueryParser queryParser : queryParsers) {
qpBinders.addBinding().toInstance(queryParser);
}
qpBinders.addBinding().to(MatchQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(MultiMatchQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(NestedQueryParser.class).asEagerSingleton();

View File

@ -30,9 +30,6 @@ import org.elasticsearch.index.query.QueryParser;
import java.util.Map;
import java.util.Set;
/**
*
*/
public class IndicesQueriesRegistry extends AbstractComponent {
private ImmutableMap<String, QueryParser> queryParsers;
@ -42,27 +39,17 @@ public class IndicesQueriesRegistry extends AbstractComponent {
super(settings);
Map<String, QueryParser> queryParsers = Maps.newHashMap();
for (QueryParser queryParser : injectedQueryParsers) {
addQueryParser(queryParsers, queryParser);
for (String name : queryParser.names()) {
queryParsers.put(name, queryParser);
}
}
this.queryParsers = ImmutableMap.copyOf(queryParsers);
}
/**
* Adds a global query parser.
* Returns all the registered query parsers
*/
public synchronized void addQueryParser(QueryParser queryParser) {
Map<String, QueryParser> queryParsers = Maps.newHashMap(this.queryParsers);
addQueryParser(queryParsers, queryParser);
this.queryParsers = ImmutableMap.copyOf(queryParsers);
}
public ImmutableMap<String, QueryParser> queryParsers() {
return queryParsers;
}
private void addQueryParser(Map<String, QueryParser> queryParsers, QueryParser queryParser) {
for (String name : queryParser.names()) {
queryParsers.put(name, queryParser);
}
}
}

View File

@ -41,7 +41,6 @@ import org.elasticsearch.action.termvectors.*;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.MoreLikeThisQuery;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.function.BoostScoreFunction;
@ -50,12 +49,9 @@ import org.elasticsearch.common.lucene.search.function.WeightFactorFunction;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.ParsedDocument;
@ -71,7 +67,6 @@ import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
@ -83,61 +78,13 @@ import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery;
import static org.hamcrest.Matchers.*;
/**
*
*/
public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest {
private IndexQueryParserService queryParser;
private static class DummyQuery extends Query {
public boolean isFilter;
@Override
public String toString(String field) {
return getClass().getSimpleName();
}
}
public static class DummyQueryParser extends AbstractIndexComponent implements QueryParser {
@Inject
public DummyQueryParser(Index index, Settings indexSettings) {
super(index, indexSettings);
}
@Override
public String[] names() {
return new String[] {"dummy"};
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
assertEquals(XContentParser.Token.END_OBJECT, parseContext.parser().nextToken());
DummyQuery query = new DummyQuery();
query.isFilter = parseContext.isFilter();
return query;
}
}
private static class DummyQueryBuilder extends QueryBuilder {
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("dummy").endObject();
}
}
private static DummyQueryBuilder dummyQuery() {
return new DummyQueryBuilder();
}
@Before
public void setup() throws IOException {
Settings settings = Settings.settingsBuilder()
.put("index.queryparser.query.dummy.type", DummyQueryParser.class)
.put("index.cache.filter.type", "none")
.put("name", "SimpleIndexQueryParserTests")
.build();
@ -1063,7 +1010,6 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest {
assertThat(clauses[3].getOccur(), equalTo(BooleanClause.Occur.SHOULD));
}
@Test
public void testBoolQuery() throws IOException {
IndexQueryParserService queryParser = queryParser();
@ -1964,7 +1910,6 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest {
assertThat(filter.bottomRight().lon(), closeTo(-80, 0.00001));
}
@Test
public void testGeoBoundingBoxFilter1() throws IOException {
IndexQueryParserService queryParser = queryParser();
@ -2464,72 +2409,11 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest {
public void testTermsQueryFilter() throws Exception {
// TermsQuery is tricky in that it parses differently as a query or a filter
IndexQueryParserService queryParser = queryParser();
Query q = queryParser.parse(termsQuery("foo", Arrays.asList("bar"))).query();
Query q = queryParser.parse(termsQuery("foo", "bar")).query();
assertThat(q, instanceOf(BooleanQuery.class));
ConstantScoreQuery csq = (ConstantScoreQuery) queryParser.parse(constantScoreQuery(termsQuery("foo", Arrays.asList("bar")))).query();
ConstantScoreQuery csq = (ConstantScoreQuery) queryParser.parse(constantScoreQuery(termsQuery("foo", "bar"))).query();
q = csq.getQuery();
assertThat(q, instanceOf(TermsQuery.class));
}
public void testConstantScoreParsesFilter() throws Exception {
IndexQueryParserService queryParser = queryParser();
Query q = queryParser.parse(constantScoreQuery(dummyQuery())).query();
Query inner = ((ConstantScoreQuery) q).getQuery();
assertThat(inner, instanceOf(DummyQuery.class));
assertEquals(true, ((DummyQuery) inner).isFilter);
}
public void testBooleanParsesFilter() throws Exception {
IndexQueryParserService queryParser = queryParser();
// single clause, serialized as inner object
Query q = queryParser.parse(boolQuery()
.should(dummyQuery())
.must(dummyQuery())
.filter(dummyQuery())
.mustNot(dummyQuery())).query();
assertThat(q, instanceOf(BooleanQuery.class));
BooleanQuery bq = (BooleanQuery) q;
assertEquals(4, bq.clauses().size());
for (BooleanClause clause : bq.clauses()) {
DummyQuery dummy = (DummyQuery) clause.getQuery();
switch (clause.getOccur()) {
case FILTER:
case MUST_NOT:
assertEquals(true, dummy.isFilter);
break;
case MUST:
case SHOULD:
assertEquals(false, dummy.isFilter);
break;
default:
throw new AssertionError();
}
}
// multiple clauses, serialized as inner arrays
q = queryParser.parse(boolQuery()
.should(dummyQuery()).should(dummyQuery())
.must(dummyQuery()).must(dummyQuery())
.filter(dummyQuery()).filter(dummyQuery())
.mustNot(dummyQuery()).mustNot(dummyQuery())).query();
assertThat(q, instanceOf(BooleanQuery.class));
bq = (BooleanQuery) q;
assertEquals(8, bq.clauses().size());
for (BooleanClause clause : bq.clauses()) {
DummyQuery dummy = (DummyQuery) clause.getQuery();
switch (clause.getOccur()) {
case FILTER:
case MUST_NOT:
assertEquals(true, dummy.isFilter);
break;
case MUST:
case SHOULD:
assertEquals(false, dummy.isFilter);
break;
default:
throw new AssertionError();
}
}
}
}

View File

@ -83,7 +83,6 @@ public class TemplateQueryParserTest extends ElasticsearchTestCase {
new AnalysisModule(settings),
new SimilarityModule(settings),
new IndexNameModule(index),
new IndexQueryParserModule(settings),
new FunctionScoreModule(),
new AbstractModule() {
@Override

View File

@ -1,51 +0,0 @@
/*
* 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.query.guice;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.junit.Test;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class IndexQueryParserModuleTests extends ElasticsearchSingleNodeTest {
@Test
public void testCustomInjection() {
Settings settings = settingsBuilder()
.put("index.queryparser.query.my.type", MyJsonQueryParser.class)
.put("index.queryparser.query.my.param1", "value1")
.put("index.cache.filter.type", "none")
.put("name", "IndexQueryParserModuleTests")
.build();
IndexQueryParserService indexQueryParserService = createIndex("test", settings).queryParserService();
MyJsonQueryParser myJsonQueryParser = (MyJsonQueryParser) indexQueryParserService.queryParser("my");
assertThat(myJsonQueryParser.names()[0], equalTo("my"));
assertThat(myJsonQueryParser.settings().get("param1"), equalTo("value1"));
}
}

View File

@ -1,64 +0,0 @@
/*
* 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.query.guice;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.assistedinject.Assisted;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.settings.IndexSettings;
import java.io.IOException;
/**
*
*/
public class MyJsonQueryParser extends AbstractIndexComponent implements QueryParser {
private final String name;
private final Settings settings;
@Inject
public MyJsonQueryParser(Index index, @IndexSettings Settings indexSettings, @Assisted String name, @Assisted Settings settings) {
super(index, indexSettings);
this.name = name;
this.settings = settings;
}
@Override
public String[] names() {
return new String[]{this.name};
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
return null;
}
public Settings settings() {
return settings;
}
}

View File

@ -1,99 +0,0 @@
/*
* 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.query.plugin;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.inject.ModulesBuilder;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.EnvironmentModule;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNameModule;
import org.elasticsearch.index.analysis.AnalysisModule;
import org.elasticsearch.index.cache.IndexCacheModule;
import org.elasticsearch.index.query.IndexQueryParserModule;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.functionscore.FunctionScoreModule;
import org.elasticsearch.index.settings.IndexSettingsModule;
import org.elasticsearch.index.similarity.SimilarityModule;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.indices.query.IndicesQueriesModule;
import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.threadpool.ThreadPoolModule;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class IndexQueryParserPlugin2Tests extends ElasticsearchTestCase {
@Test
public void testCustomInjection() throws InterruptedException {
Settings settings = Settings.builder()
.put("name", "testCustomInjection")
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put("path.home", createTempDir()).build();
IndexQueryParserModule queryParserModule = new IndexQueryParserModule(settings);
queryParserModule.addQueryParser("my", PluginJsonQueryParser.class);
Index index = new Index("test");
Injector injector = new ModulesBuilder().add(
new EnvironmentModule(new Environment(settings)),
new SettingsModule(settings),
new ThreadPoolModule(new ThreadPool(settings)),
new IndicesQueriesModule(),
new ScriptModule(settings),
new IndexSettingsModule(index, settings),
new IndexCacheModule(settings),
new AnalysisModule(settings),
new SimilarityModule(settings),
queryParserModule,
new IndexNameModule(index),
new FunctionScoreModule(),
new AbstractModule() {
@Override
protected void configure() {
bind(ClusterService.class).toProvider(Providers.of((ClusterService) null));
bind(CircuitBreakerService.class).to(NoneCircuitBreakerService.class);
}
}
).createInjector();
IndexQueryParserService indexQueryParserService = injector.getInstance(IndexQueryParserService.class);
PluginJsonQueryParser myJsonQueryParser = (PluginJsonQueryParser) indexQueryParserService.queryParser("my");
assertThat(myJsonQueryParser.names()[0], equalTo("my"));
terminate(injector.getInstance(ThreadPool.class));
}
}

View File

@ -1,104 +0,0 @@
/*
* 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.query.plugin;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.inject.ModulesBuilder;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.EnvironmentModule;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNameModule;
import org.elasticsearch.index.analysis.AnalysisModule;
import org.elasticsearch.index.cache.IndexCacheModule;
import org.elasticsearch.index.query.IndexQueryParserModule;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.functionscore.FunctionScoreModule;
import org.elasticsearch.index.settings.IndexSettingsModule;
import org.elasticsearch.index.similarity.SimilarityModule;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.indices.query.IndicesQueriesModule;
import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.threadpool.ThreadPoolModule;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class IndexQueryParserPluginTests extends ElasticsearchTestCase {
@Test
public void testCustomInjection() throws InterruptedException {
Settings settings = Settings.builder()
.put("name", "testCustomInjection")
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put("path.home", createTempDir()).build();
IndexQueryParserModule queryParserModule = new IndexQueryParserModule(settings);
queryParserModule.addProcessor(new IndexQueryParserModule.QueryParsersProcessor() {
@Override
public void processXContentQueryParsers(XContentQueryParsersBindings bindings) {
bindings.processXContentQueryParser("my", PluginJsonQueryParser.class);
}
});
Index index = new Index("test");
Injector injector = new ModulesBuilder().add(
new EnvironmentModule(new Environment(settings)),
new SettingsModule(settings),
new ThreadPoolModule(new ThreadPool(settings)),
new IndicesQueriesModule(),
new ScriptModule(settings),
new IndexSettingsModule(index, settings),
new IndexCacheModule(settings),
new AnalysisModule(settings),
new SimilarityModule(settings),
queryParserModule,
new IndexNameModule(index),
new FunctionScoreModule(),
new AbstractModule() {
@Override
protected void configure() {
bind(ClusterService.class).toProvider(Providers.of((ClusterService) null));
bind(CircuitBreakerService.class).to(NoneCircuitBreakerService.class);
}
}
).createInjector();
IndexQueryParserService indexQueryParserService = injector.getInstance(IndexQueryParserService.class);
PluginJsonQueryParser myJsonQueryParser = (PluginJsonQueryParser) indexQueryParserService.queryParser("my");
assertThat(myJsonQueryParser.names()[0], equalTo("my"));
terminate(injector.getInstance(ThreadPool.class));
}
}

View File

@ -1,64 +0,0 @@
/*
* 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.query.plugin;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.assistedinject.Assisted;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.settings.IndexSettings;
import java.io.IOException;
/**
*
*/
public class PluginJsonQueryParser extends AbstractIndexComponent implements QueryParser {
private final String name;
private final Settings settings;
@Inject
public PluginJsonQueryParser(Index index, @IndexSettings Settings indexSettings, @Assisted String name, @Assisted Settings settings) {
super(index, indexSettings);
this.name = name;
this.settings = settings;
}
@Override
public String[] names() {
return new String[]{this.name};
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
return null;
}
public Settings settings() {
return settings;
}
}

View File

@ -490,6 +490,10 @@ ignored. Instead filters are always used as their own cache key and elasticsearc
makes decisions by itself about whether it should cache filters based on how
often they are used.
Java plugins that register custom queries can do so by using the
`IndicesQueriesModule#addQuery(Class<? extends QueryParser>)` method. Other
ways to register custom queries are not supported anymore.
==== Query/filter merge
Elasticsearch no longer makes a difference between queries and filters in the

View File

@ -0,0 +1,138 @@
/*
* 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.query.plugin;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.instanceOf;
public class CustomQueryParserTests extends ElasticsearchIntegrationTest {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder().put(super.nodeSettings(nodeOrdinal))
.put("plugin.types", DummyQueryParserPlugin.class.getName()).build();
}
@Before
public void setUp() throws Exception {
super.setUp();
createIndex("test");
ensureGreen();
client().prepareIndex("index", "type", "1").setSource("field", "value").get();
refresh();
}
@Override
protected int numberOfShards() {
return cluster().numDataNodes();
}
@Test
public void testCustomDummyQuery() {
assertHitCount(client().prepareSearch("index").setQuery(new DummyQueryParserPlugin.DummyQueryBuilder()).get(), 1l);
}
@Test
public void testCustomDummyQueryWithinBooleanQuery() {
assertHitCount(client().prepareSearch("index").setQuery(new BoolQueryBuilder().must(new DummyQueryParserPlugin.DummyQueryBuilder())).get(), 1l);
}
private static IndexQueryParserService queryParser() {
IndicesService indicesService = internalCluster().getDataNodeInstance(IndicesService.class);
return indicesService.indexServiceSafe("index").queryParserService();
}
@Test //see #11120
public void testConstantScoreParsesFilter() throws Exception {
IndexQueryParserService queryParser = queryParser();
Query q = queryParser.parse(constantScoreQuery(new DummyQueryParserPlugin.DummyQueryBuilder())).query();
Query inner = ((ConstantScoreQuery) q).getQuery();
assertThat(inner, instanceOf(DummyQueryParserPlugin.DummyQuery.class));
assertEquals(true, ((DummyQueryParserPlugin.DummyQuery) inner).isFilter);
}
@Test //see #11120
public void testBooleanParsesFilter() throws Exception {
IndexQueryParserService queryParser = queryParser();
// single clause, serialized as inner object
Query q = queryParser.parse(boolQuery()
.should(new DummyQueryParserPlugin.DummyQueryBuilder())
.must(new DummyQueryParserPlugin.DummyQueryBuilder())
.filter(new DummyQueryParserPlugin.DummyQueryBuilder())
.mustNot(new DummyQueryParserPlugin.DummyQueryBuilder())).query();
assertThat(q, instanceOf(BooleanQuery.class));
BooleanQuery bq = (BooleanQuery) q;
assertEquals(4, bq.clauses().size());
for (BooleanClause clause : bq.clauses()) {
DummyQueryParserPlugin.DummyQuery dummy = (DummyQueryParserPlugin.DummyQuery) clause.getQuery();
switch (clause.getOccur()) {
case FILTER:
case MUST_NOT:
assertEquals(true, dummy.isFilter);
break;
case MUST:
case SHOULD:
assertEquals(false, dummy.isFilter);
break;
default:
throw new AssertionError();
}
}
// multiple clauses, serialized as inner arrays
q = queryParser.parse(boolQuery()
.should(new DummyQueryParserPlugin.DummyQueryBuilder()).should(new DummyQueryParserPlugin.DummyQueryBuilder())
.must(new DummyQueryParserPlugin.DummyQueryBuilder()).must(new DummyQueryParserPlugin.DummyQueryBuilder())
.filter(new DummyQueryParserPlugin.DummyQueryBuilder()).filter(new DummyQueryParserPlugin.DummyQueryBuilder())
.mustNot(new DummyQueryParserPlugin.DummyQueryBuilder()).mustNot(new DummyQueryParserPlugin.DummyQueryBuilder())).query();
assertThat(q, instanceOf(BooleanQuery.class));
bq = (BooleanQuery) q;
assertEquals(8, bq.clauses().size());
for (BooleanClause clause : bq.clauses()) {
DummyQueryParserPlugin.DummyQuery dummy = (DummyQueryParserPlugin.DummyQuery) clause.getQuery();
switch (clause.getOccur()) {
case FILTER:
case MUST_NOT:
assertEquals(true, dummy.isFilter);
break;
case MUST:
case SHOULD:
assertEquals(false, dummy.isFilter);
break;
default:
throw new AssertionError();
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.query.plugin;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Weight;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.indices.query.IndicesQueriesModule;
import org.elasticsearch.plugins.AbstractPlugin;
import java.io.IOException;
public class DummyQueryParserPlugin extends AbstractPlugin {
@Override
public String name() {
return "dummy";
}
@Override
public String description() {
return "dummy query";
}
@Override
public void processModule(Module module) {
if (module instanceof IndicesQueriesModule) {
IndicesQueriesModule indicesQueriesModule = (IndicesQueriesModule) module;
indicesQueriesModule.addQuery(DummyQueryParserPlugin.DummyQueryParser.class);
}
}
public Settings settings() {
return Settings.EMPTY;
}
public static class DummyQueryBuilder extends QueryBuilder {
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("dummy").endObject();
}
}
public static class DummyQueryParser implements QueryParser {
@Override
public String[] names() {
return new String[]{"dummy"};
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser.Token token = parseContext.parser().nextToken();
assert token == XContentParser.Token.END_OBJECT;
return new DummyQuery(parseContext.isFilter());
}
}
public static class DummyQuery extends Query {
public final boolean isFilter;
private final Query matchAllDocsQuery = new MatchAllDocsQuery();
private DummyQuery(boolean isFilter) {
this.isFilter = isFilter;
}
@Override
public String toString(String field) {
return getClass().getSimpleName();
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return matchAllDocsQuery.createWeight(searcher, needsScores);
}
}
}