Skip to content

Commit 9a73973

Browse files
committed
Accept an array of field names and boosts in the index.query.default_field setting (#26320)
* Accept an array of field names and boosts in the index.query.default_field setting This commit allows to define an array of field names and boosts for the index setting `index.query.default_field`. The format is equivalent to the `fields` options of the full text search queries (e.g. field_name^boost). This commit also makes this setting dynamically updatable. Fixes #25946
1 parent 6a90872 commit 9a73973

File tree

12 files changed

+171
-81
lines changed

12 files changed

+171
-81
lines changed

core/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
import org.elasticsearch.index.shard.ShardId;
6262
import org.elasticsearch.indices.IndicesService;
6363
import org.elasticsearch.indices.analysis.AnalysisModule;
64-
import org.elasticsearch.indices.analysis.PreBuiltTokenizers;
6564
import org.elasticsearch.threadpool.ThreadPool;
6665
import org.elasticsearch.transport.TransportService;
6766

@@ -151,8 +150,12 @@ protected AnalyzeResponse shardOperation(AnalyzeRequest request, ShardId shardId
151150
}
152151
}
153152
if (field == null) {
153+
/**
154+
* TODO: _all is disabled by default and index.query.default_field can define multiple fields or pattterns so we should
155+
* probably makes the field name mandatory in analyze query.
156+
**/
154157
if (indexService != null) {
155-
field = indexService.getIndexSettings().getDefaultField();
158+
field = indexService.getIndexSettings().getDefaultFields().get(0);
156159
} else {
157160
field = AllFieldMapper.NAME;
158161
}

core/src/main/java/org/elasticsearch/index/IndexSettings.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.elasticsearch.Version;
2424
import org.elasticsearch.cluster.metadata.IndexMetaData;
2525
import org.elasticsearch.common.logging.Loggers;
26-
import org.elasticsearch.common.lucene.all.AllField;
2726
import org.elasticsearch.common.settings.IndexScopedSettings;
2827
import org.elasticsearch.common.settings.Setting;
2928
import org.elasticsearch.common.settings.Setting.Property;
@@ -32,10 +31,11 @@
3231
import org.elasticsearch.common.unit.ByteSizeValue;
3332
import org.elasticsearch.common.unit.TimeValue;
3433
import org.elasticsearch.index.mapper.AllFieldMapper;
35-
import org.elasticsearch.index.mapper.MapperService;
3634
import org.elasticsearch.index.translog.Translog;
3735
import org.elasticsearch.node.Node;
3836

37+
import java.util.Collections;
38+
import java.util.List;
3939
import java.util.Locale;
4040
import java.util.concurrent.TimeUnit;
4141
import java.util.function.Consumer;
@@ -50,19 +50,19 @@
5050
*/
5151
public final class IndexSettings {
5252
public static final String DEFAULT_FIELD_SETTING_KEY = "index.query.default_field";
53-
public static final Setting<String> DEFAULT_FIELD_SETTING;
53+
public static final Setting<List<String>> DEFAULT_FIELD_SETTING;
5454
static {
55-
Function<Settings, String> defValue = settings -> {
55+
Function<Settings, List<String>> defValue = settings -> {
5656
final String defaultField;
5757
if (settings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, null) != null &&
5858
Version.indexCreated(settings).before(Version.V_6_0_0_alpha1)) {
5959
defaultField = AllFieldMapper.NAME;
6060
} else {
6161
defaultField = "*";
6262
}
63-
return defaultField;
63+
return Collections.singletonList(defaultField);
6464
};
65-
DEFAULT_FIELD_SETTING = new Setting<>(DEFAULT_FIELD_SETTING_KEY, defValue, Function.identity(), Property.IndexScope, Property.Dynamic);
65+
DEFAULT_FIELD_SETTING = Setting.listSetting(DEFAULT_FIELD_SETTING_KEY, defValue, Function.identity(), Property.IndexScope, Property.Dynamic);
6666
}
6767
public static final Setting<Boolean> QUERY_STRING_LENIENT_SETTING =
6868
Setting.boolSetting("index.query_string.lenient", false, Property.IndexScope);
@@ -205,7 +205,7 @@ public final class IndexSettings {
205205
// volatile fields are updated via #updateIndexMetaData(IndexMetaData) under lock
206206
private volatile Settings settings;
207207
private volatile IndexMetaData indexMetaData;
208-
private final String defaultField;
208+
private volatile List<String> defaultFields;
209209
private final boolean queryStringLenient;
210210
private final boolean queryStringAnalyzeWildcard;
211211
private final boolean queryStringAllowLeadingWildcard;
@@ -241,10 +241,14 @@ public final class IndexSettings {
241241
private final boolean singleType;
242242

243243
/**
244-
* Returns the default search field for this index.
244+
* Returns the default search fields for this index.
245245
*/
246-
public String getDefaultField() {
247-
return defaultField;
246+
public List<String> getDefaultFields() {
247+
return defaultFields;
248+
}
249+
250+
private void setDefaultFields(List<String> defaultFields) {
251+
this.defaultFields = defaultFields;
248252
}
249253

250254
/**
@@ -304,12 +308,12 @@ public IndexSettings(final IndexMetaData indexMetaData, final Settings nodeSetti
304308
this.indexMetaData = indexMetaData;
305309
numberOfShards = settings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, null);
306310

307-
this.defaultField = DEFAULT_FIELD_SETTING.get(settings);
308311
this.queryStringLenient = QUERY_STRING_LENIENT_SETTING.get(settings);
309312
this.queryStringAnalyzeWildcard = QUERY_STRING_ANALYZE_WILDCARD.get(nodeSettings);
310313
this.queryStringAllowLeadingWildcard = QUERY_STRING_ALLOW_LEADING_WILDCARD.get(nodeSettings);
311314
this.defaultAllowUnmappedFields = scopedSettings.get(ALLOW_UNMAPPED);
312315
this.durability = scopedSettings.get(INDEX_TRANSLOG_DURABILITY_SETTING);
316+
defaultFields = scopedSettings.get(DEFAULT_FIELD_SETTING);
313317
syncInterval = INDEX_TRANSLOG_SYNC_INTERVAL_SETTING.get(settings);
314318
refreshInterval = scopedSettings.get(INDEX_REFRESH_INTERVAL_SETTING);
315319
flushThresholdSize = scopedSettings.get(INDEX_TRANSLOG_FLUSH_THRESHOLD_SIZE_SETTING);
@@ -361,6 +365,7 @@ public IndexSettings(final IndexMetaData indexMetaData, final Settings nodeSetti
361365
scopedSettings.addSettingsUpdateConsumer(INDEX_REFRESH_INTERVAL_SETTING, this::setRefreshInterval);
362366
scopedSettings.addSettingsUpdateConsumer(MAX_REFRESH_LISTENERS_PER_SHARD, this::setMaxRefreshListeners);
363367
scopedSettings.addSettingsUpdateConsumer(MAX_SLICES_PER_SCROLL, this::setMaxSlicesPerScroll);
368+
scopedSettings.addSettingsUpdateConsumer(DEFAULT_FIELD_SETTING, this::setDefaultFields);
364369
}
365370

366371
private void setTranslogFlushThresholdSize(ByteSizeValue byteSizeValue) {

core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
import java.io.IOException;
6060
import java.util.ArrayList;
6161
import java.util.Arrays;
62-
import java.util.Collections;
6362
import java.util.HashSet;
6463
import java.util.List;
6564
import java.util.Locale;
@@ -1033,7 +1032,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
10331032
boolean useDefaultField = (fields == null);
10341033
List<String> moreLikeFields = new ArrayList<>();
10351034
if (useDefaultField) {
1036-
moreLikeFields = Collections.singletonList(context.defaultField());
1035+
moreLikeFields = context.defaultFields();
10371036
} else {
10381037
for (String field : fields) {
10391038
MappedFieldType fieldType = context.fieldMapper(field);

core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,11 @@
4040
import org.elasticsearch.index.analysis.IndexAnalyzers;
4141
import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
4242
import org.elasticsearch.index.fielddata.IndexFieldData;
43-
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
4443
import org.elasticsearch.index.mapper.ContentPath;
4544
import org.elasticsearch.index.mapper.DocumentMapper;
46-
import org.elasticsearch.index.mapper.IndexFieldMapper;
4745
import org.elasticsearch.index.mapper.MappedFieldType;
4846
import org.elasticsearch.index.mapper.Mapper;
4947
import org.elasticsearch.index.mapper.MapperService;
50-
import org.elasticsearch.index.mapper.MetadataFieldMapper;
5148
import org.elasticsearch.index.mapper.ObjectMapper;
5249
import org.elasticsearch.index.mapper.TextFieldMapper;
5350
import org.elasticsearch.index.query.support.NestedScope;
@@ -60,10 +57,10 @@
6057
import java.util.Arrays;
6158
import java.util.Collection;
6259
import java.util.HashMap;
60+
import java.util.List;
6361
import java.util.Map;
6462
import java.util.function.BiConsumer;
6563
import java.util.function.BiFunction;
66-
import java.util.function.Function;
6764
import java.util.function.LongSupplier;
6865

6966
import static java.util.Collections.unmodifiableMap;
@@ -144,8 +141,8 @@ public Similarity getSearchSimilarity() {
144141
return similarityService != null ? similarityService.similarity(mapperService) : null;
145142
}
146143

147-
public String defaultField() {
148-
return indexSettings.getDefaultField();
144+
public List<String> defaultFields() {
145+
return indexSettings.getDefaultFields();
149146
}
150147

151148
public boolean queryStringLenient() {

core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
import java.io.IOException;
4444
import java.util.ArrayList;
45-
import java.util.HashMap;
45+
import java.util.Collections;
4646
import java.util.List;
4747
import java.util.Locale;
4848
import java.util.Map;
@@ -738,31 +738,18 @@ public static QueryStringQueryBuilder fromXContent(XContentParser parser) throws
738738
Fuzziness fuzziness = QueryStringQueryBuilder.DEFAULT_FUZZINESS;
739739
String fuzzyRewrite = null;
740740
String rewrite = null;
741-
Map<String, Float> fieldsAndWeights = new HashMap<>();
741+
Map<String, Float> fieldsAndWeights = null;
742742
boolean autoGenerateSynonymsPhraseQuery = true;
743743
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
744744
if (token == XContentParser.Token.FIELD_NAME) {
745745
currentFieldName = parser.currentName();
746746
} else if (token == XContentParser.Token.START_ARRAY) {
747747
if (FIELDS_FIELD.match(currentFieldName)) {
748-
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
749-
String fField = null;
750-
float fBoost = AbstractQueryBuilder.DEFAULT_BOOST;
751-
char[] text = parser.textCharacters();
752-
int end = parser.textOffset() + parser.textLength();
753-
for (int i = parser.textOffset(); i < end; i++) {
754-
if (text[i] == '^') {
755-
int relativeLocation = i - parser.textOffset();
756-
fField = new String(text, parser.textOffset(), relativeLocation);
757-
fBoost = Float.parseFloat(new String(text, i + 1, parser.textLength() - relativeLocation - 1));
758-
break;
759-
}
760-
}
761-
if (fField == null) {
762-
fField = parser.text();
763-
}
764-
fieldsAndWeights.put(fField, fBoost);
748+
List<String> fields = new ArrayList<>();
749+
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
750+
fields.add(parser.text());
765751
}
752+
fieldsAndWeights = QueryParserHelper.parseFieldsAndWeights(fields);
766753
} else {
767754
throw new ParsingException(parser.getTokenLocation(), "[" + QueryStringQueryBuilder.NAME +
768755
"] query does not support [" + currentFieldName + "]");
@@ -851,7 +838,9 @@ public static QueryStringQueryBuilder fromXContent(XContentParser parser) throws
851838
}
852839

853840
QueryStringQueryBuilder queryStringQuery = new QueryStringQueryBuilder(queryString);
854-
queryStringQuery.fields(fieldsAndWeights);
841+
if (fieldsAndWeights != null) {
842+
queryStringQuery.fields(fieldsAndWeights);
843+
}
855844
queryStringQuery.defaultField(defaultField);
856845
queryStringQuery.defaultOperator(defaultOperator);
857846
queryStringQuery.analyzer(analyzer);
@@ -943,16 +932,19 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
943932
final Map<String, Float> resolvedFields = QueryParserHelper.resolveMappingFields(context, fieldsAndWeights);
944933
queryParser = new QueryStringQueryParser(context, resolvedFields, isLenient);
945934
} else {
946-
String defaultField = context.defaultField();
935+
List<String> defaultFields = context.defaultFields();
947936
if (context.getMapperService().allEnabled() == false &&
948-
AllFieldMapper.NAME.equals(defaultField)) {
937+
defaultFields.size() == 1 && AllFieldMapper.NAME.equals(defaultFields.get(0))) {
949938
// For indices created before 6.0 with _all disabled
950-
defaultField = "*";
939+
defaultFields = Collections.singletonList("*");
951940
}
952-
if (Regex.isMatchAllPattern(defaultField)) {
941+
boolean isAllField = defaultFields.size() == 1 && Regex.isMatchAllPattern(defaultFields.get(0));
942+
if (isAllField) {
953943
queryParser = new QueryStringQueryParser(context, lenient == null ? true : lenient);
954944
} else {
955-
queryParser = new QueryStringQueryParser(context, defaultField, isLenient);
945+
final Map<String, Float> resolvedFields = QueryParserHelper.resolveMappingFields(context,
946+
QueryParserHelper.parseFieldsAndWeights(defaultFields));
947+
queryParser = new QueryStringQueryParser(context, resolvedFields, isLenient);
956948
}
957949
}
958950

core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
import org.elasticsearch.index.search.SimpleQueryStringQueryParser.Settings;
3838

3939
import java.io.IOException;
40+
import java.util.ArrayList;
4041
import java.util.Collections;
4142
import java.util.HashMap;
43+
import java.util.List;
4244
import java.util.Locale;
4345
import java.util.Map;
4446
import java.util.Objects;
@@ -402,18 +404,18 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
402404
if (fieldsAndWeights.isEmpty() == false) {
403405
resolvedFieldsAndWeights = QueryParserHelper.resolveMappingFields(context, fieldsAndWeights);
404406
} else {
405-
String defaultField = context.defaultField();
407+
List<String> defaultFields = context.defaultFields();
406408
if (context.getMapperService().allEnabled() == false &&
407-
AllFieldMapper.NAME.equals(defaultField)) {
409+
defaultFields.size() == 1 && AllFieldMapper.NAME.equals(defaultFields.get(0))) {
408410
// For indices created before 6.0 with _all disabled
409-
defaultField = "*";
411+
defaultFields = Collections.singletonList("*");
410412
}
411-
boolean isAllField = Regex.isMatchAllPattern(defaultField);
413+
boolean isAllField = defaultFields.size() == 1 && Regex.isMatchAllPattern(defaultFields.get(0));
412414
if (isAllField) {
413415
newSettings.lenient(lenientSet ? settings.lenient() : true);
414416
}
415-
resolvedFieldsAndWeights = QueryParserHelper.resolveMappingField(context, defaultField, 1.0f,
416-
false, !isAllField);
417+
resolvedFieldsAndWeights = QueryParserHelper.resolveMappingFields(context,
418+
QueryParserHelper.parseFieldsAndWeights(defaultFields));
417419
}
418420

419421
final SimpleQueryStringQueryParser sqp;
@@ -474,7 +476,7 @@ public static SimpleQueryStringBuilder fromXContent(XContentParser parser) throw
474476
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
475477
String queryName = null;
476478
String minimumShouldMatch = null;
477-
Map<String, Float> fieldsAndWeights = new HashMap<>();
479+
Map<String, Float> fieldsAndWeights = null;
478480
Operator defaultOperator = null;
479481
String analyzerName = null;
480482
int flags = SimpleQueryStringFlag.ALL.value();
@@ -489,24 +491,11 @@ public static SimpleQueryStringBuilder fromXContent(XContentParser parser) throw
489491
currentFieldName = parser.currentName();
490492
} else if (token == XContentParser.Token.START_ARRAY) {
491493
if (FIELDS_FIELD.match(currentFieldName)) {
492-
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
493-
String fField = null;
494-
float fBoost = 1;
495-
char[] text = parser.textCharacters();
496-
int end = parser.textOffset() + parser.textLength();
497-
for (int i = parser.textOffset(); i < end; i++) {
498-
if (text[i] == '^') {
499-
int relativeLocation = i - parser.textOffset();
500-
fField = new String(text, parser.textOffset(), relativeLocation);
501-
fBoost = Float.parseFloat(new String(text, i + 1, parser.textLength() - relativeLocation - 1));
502-
break;
503-
}
504-
}
505-
if (fField == null) {
506-
fField = parser.text();
507-
}
508-
fieldsAndWeights.put(fField, fBoost);
494+
List<String> fields = new ArrayList<>();
495+
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
496+
fields.add(parser.text());
509497
}
498+
fieldsAndWeights = QueryParserHelper.parseFieldsAndWeights(fields);
510499
} else {
511500
throw new ParsingException(parser.getTokenLocation(), "[" + SimpleQueryStringBuilder.NAME +
512501
"] query does not support [" + currentFieldName + "]");
@@ -565,7 +554,10 @@ public static SimpleQueryStringBuilder fromXContent(XContentParser parser) throw
565554
}
566555

567556
SimpleQueryStringBuilder qb = new SimpleQueryStringBuilder(queryBody);
568-
qb.boost(boost).fields(fieldsAndWeights).analyzer(analyzerName).queryName(queryName).minimumShouldMatch(minimumShouldMatch);
557+
if (fieldsAndWeights != null) {
558+
qb.fields(fieldsAndWeights);
559+
}
560+
qb.boost(boost).analyzer(analyzerName).queryName(queryName).minimumShouldMatch(minimumShouldMatch);
569561
qb.flags(flags).defaultOperator(defaultOperator);
570562
if (lenient != null) {
571563
qb.lenient(lenient);

core/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
import java.util.Collection;
3636
import java.util.HashMap;
3737
import java.util.HashSet;
38+
import java.util.List;
3839
import java.util.Map;
3940
import java.util.Set;
4041

4142
/**
42-
* Helpers to extract and expand field names from a mapping
43+
* Helpers to extract and expand field names and boosts
4344
*/
4445
public final class QueryParserHelper {
4546
// Mapping types the "all-ish" query can be executed against
@@ -59,6 +60,29 @@ public final class QueryParserHelper {
5960

6061
private QueryParserHelper() {}
6162

63+
/**
64+
* Convert a list of field names encoded with optional boosts to a map that associates
65+
* the field name and its boost.
66+
* @param fields The list of fields encoded with optional boosts (e.g. ^0.35).
67+
* @return The converted map with field names and associated boosts.
68+
*/
69+
public static Map<String, Float> parseFieldsAndWeights(List<String> fields) {
70+
final Map<String, Float> fieldsAndWeights = new HashMap<>();
71+
for (String field : fields) {
72+
int boostIndex = field.indexOf('^');
73+
String fieldName;
74+
float boost = 1.0f;
75+
if (boostIndex != -1) {
76+
fieldName = field.substring(0, boostIndex);
77+
boost = Float.parseFloat(field.substring(boostIndex+1, field.length()));
78+
} else {
79+
fieldName = field;
80+
}
81+
fieldsAndWeights.put(fieldName, boost);
82+
}
83+
return fieldsAndWeights;
84+
}
85+
6286
/**
6387
* Get a {@link FieldMapper} associated with a field name or null.
6488
* @param mapperService The mapper service where to find the mapping.

0 commit comments

Comments
 (0)