|
@@ -34,6 +34,10 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
|
import org.elasticsearch.common.xcontent.XContentParser;
|
|
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
|
|
import org.elasticsearch.index.analysis.NamedAnalyzer;
|
|
|
+import org.elasticsearch.index.fielddata.IndexFieldData;
|
|
|
+import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData;
|
|
|
+import org.elasticsearch.index.fielddata.plain.PagedBytesIndexFieldData;
|
|
|
+import org.elasticsearch.index.mapper.DocumentMapperParser;
|
|
|
import org.elasticsearch.index.mapper.FieldMapper;
|
|
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
|
|
import org.elasticsearch.index.mapper.Mapper;
|
|
@@ -47,6 +51,7 @@ import java.util.HashSet;
|
|
|
import java.util.Iterator;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
import java.util.Set;
|
|
|
|
|
|
import static org.apache.lucene.index.IndexOptions.NONE;
|
|
@@ -58,12 +63,20 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
public static final String CONTENT_TYPE = "string";
|
|
|
private static final int POSITION_INCREMENT_GAP_USE_ANALYZER = -1;
|
|
|
|
|
|
+ // If a string field is created on 5.x and all parameters are in this list then we
|
|
|
+ // will automatically upgrade to a text/keyword field. Otherwise we will just fail
|
|
|
+ // saying that string fields are not supported anymore.
|
|
|
private static final Set<String> SUPPORTED_PARAMETERS_FOR_AUTO_UPGRADE = new HashSet<>(Arrays.asList(
|
|
|
"type",
|
|
|
// most common parameters, for which the upgrade is straightforward
|
|
|
- "index", "store", "doc_values", "omit_norms", "norms", "fields", "copy_to"));
|
|
|
+ "index", "store", "doc_values", "omit_norms", "norms", "fields", "copy_to",
|
|
|
+ "fielddata", "ignore_above"));
|
|
|
|
|
|
public static class Defaults {
|
|
|
+ public static double FIELDDATA_MIN_FREQUENCY = 0;
|
|
|
+ public static double FIELDDATA_MAX_FREQUENCY = Integer.MAX_VALUE;
|
|
|
+ public static int FIELDDATA_MIN_SEGMENT_SIZE = 0;
|
|
|
+
|
|
|
public static final MappedFieldType FIELD_TYPE = new StringFieldType();
|
|
|
|
|
|
static {
|
|
@@ -94,6 +107,11 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
builder = this;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public StringFieldType fieldType() {
|
|
|
+ return (StringFieldType) super.fieldType();
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) {
|
|
|
super.searchAnalyzer(searchAnalyzer);
|
|
@@ -110,6 +128,31 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ public Builder fielddata(boolean fielddata) {
|
|
|
+ fieldType().setFielddata(fielddata);
|
|
|
+ return builder;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder eagerGlobalOrdinals(boolean eagerGlobalOrdinals) {
|
|
|
+ fieldType().setEagerGlobalOrdinals(eagerGlobalOrdinals);
|
|
|
+ return builder;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder fielddataFrequencyFilter(double minFreq, double maxFreq, int minSegmentSize) {
|
|
|
+ fieldType().setFielddataMinFrequency(minFreq);
|
|
|
+ fieldType().setFielddataMaxFrequency(maxFreq);
|
|
|
+ fieldType().setFielddataMinSegmentSize(minSegmentSize);
|
|
|
+ return builder;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void setupFieldType(BuilderContext context) {
|
|
|
+ super.setupFieldType(context);
|
|
|
+ if (fieldType().hasDocValues() && ((StringFieldType) fieldType()).fielddata()) {
|
|
|
+ ((StringFieldType) fieldType()).setFielddata(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public StringFieldMapper build(BuilderContext context) {
|
|
|
if (positionIncrementGap != POSITION_INCREMENT_GAP_USE_ANALYZER) {
|
|
@@ -134,7 +177,7 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
}
|
|
|
setupFieldType(context);
|
|
|
StringFieldMapper fieldMapper = new StringFieldMapper(
|
|
|
- name, fieldType, defaultFieldType, positionIncrementGap, ignoreAbove,
|
|
|
+ name, fieldType(), defaultFieldType, positionIncrementGap, ignoreAbove,
|
|
|
context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
|
|
|
return fieldMapper.includeInAll(includeInAll);
|
|
|
}
|
|
@@ -175,6 +218,28 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
node.put("norms", TypeParsers.nodeBooleanValue("omit_norms", omitNorms, parserContext) == false);
|
|
|
}
|
|
|
}
|
|
|
+ {
|
|
|
+ // upgrade fielddata settings
|
|
|
+ Object fielddataO = node.get("fielddata");
|
|
|
+ if (fielddataO instanceof Map) {
|
|
|
+ Map<?,?> fielddata = (Map<?, ?>) fielddataO;
|
|
|
+ if (keyword == false) {
|
|
|
+ node.put("fielddata", "disabled".equals(fielddata.get("format")) == false);
|
|
|
+ Map<?,?> fielddataFilter = (Map<?, ?>) fielddata.get("filter");
|
|
|
+ if (fielddataFilter != null) {
|
|
|
+ Map<?,?> frequencyFilter = (Map<?, ?>) fielddataFilter.get("frequency");
|
|
|
+ frequencyFilter.keySet().retainAll(Arrays.asList("min", "max", "min_segment_size"));
|
|
|
+ node.put("fielddata_frequency_filter", frequencyFilter);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ node.remove("fielddata");
|
|
|
+ }
|
|
|
+ final Object loading = fielddata.get("loading");
|
|
|
+ if (loading != null) {
|
|
|
+ node.put("eager_global_ordinals", "eager_global_ordinals".equals(loading));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
if (keyword) {
|
|
|
return new KeywordFieldMapper.TypeParser().parse(fieldName, node, parserContext);
|
|
|
} else {
|
|
@@ -185,6 +250,7 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
throw new IllegalArgumentException("The [string] type is removed in 5.0. You should now use either a [text] "
|
|
|
+ "or [keyword] field instead for field [" + fieldName + "]");
|
|
|
}
|
|
|
+
|
|
|
StringFieldMapper.Builder builder = new StringFieldMapper.Builder(fieldName);
|
|
|
// hack for the fact that string can't just accept true/false for
|
|
|
// the index property and still accepts no/not_analyzed/analyzed
|
|
@@ -207,6 +273,21 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
throw new IllegalArgumentException("Can't parse [index] value [" + index + "] for field [" + fieldName + "], expected [true], [false], [no], [not_analyzed] or [analyzed]");
|
|
|
}
|
|
|
}
|
|
|
+ final Object fielddataObject = node.get("fielddata");
|
|
|
+ if (fielddataObject instanceof Map) {
|
|
|
+ Map<?,?> fielddata = (Map<?, ?>) fielddataObject;
|
|
|
+ final Object loading = fielddata.get("loading");
|
|
|
+ if (loading != null) {
|
|
|
+ node.put("eager_global_ordinals", "eager_global_ordinals".equals(loading));
|
|
|
+ }
|
|
|
+ Map<?,?> fielddataFilter = (Map<?, ?>) fielddata.get("filter");
|
|
|
+ if (fielddataFilter != null) {
|
|
|
+ Map<?,?> frequencyFilter = (Map<?, ?>) fielddataFilter.get("frequency");
|
|
|
+ frequencyFilter.keySet().retainAll(Arrays.asList("min", "max", "min_segment_size"));
|
|
|
+ node.put("fielddata_frequency_filter", frequencyFilter);
|
|
|
+ }
|
|
|
+ node.put("fielddata", "disabled".equals(fielddata.get("format")) == false);
|
|
|
+ }
|
|
|
parseTextField(builder, fieldName, node, parserContext);
|
|
|
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
|
|
|
Map.Entry<String, Object> entry = iterator.next();
|
|
@@ -239,6 +320,20 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
} else if (propName.equals("ignore_above")) {
|
|
|
builder.ignoreAbove(XContentMapValues.nodeIntegerValue(propNode, -1));
|
|
|
iterator.remove();
|
|
|
+ } else if (propName.equals("fielddata")) {
|
|
|
+ builder.fielddata(XContentMapValues.nodeBooleanValue(propNode));
|
|
|
+ iterator.remove();
|
|
|
+ } else if (propName.equals("eager_global_ordinals")) {
|
|
|
+ builder.eagerGlobalOrdinals(XContentMapValues.nodeBooleanValue(propNode));
|
|
|
+ iterator.remove();
|
|
|
+ } else if (propName.equals("fielddata_frequency_filter")) {
|
|
|
+ Map<?,?> frequencyFilter = (Map<?, ?>) propNode;
|
|
|
+ double minFrequency = XContentMapValues.nodeDoubleValue(frequencyFilter.remove("min"), 0);
|
|
|
+ double maxFrequency = XContentMapValues.nodeDoubleValue(frequencyFilter.remove("max"), Integer.MAX_VALUE);
|
|
|
+ int minSegmentSize = XContentMapValues.nodeIntegerValue(frequencyFilter.remove("min_segment_size"), 0);
|
|
|
+ builder.fielddataFrequencyFilter(minFrequency, maxFrequency, minSegmentSize);
|
|
|
+ DocumentMapperParser.checkNoRemainingFields(propName, frequencyFilter, parserContext.indexVersionCreated());
|
|
|
+ iterator.remove();
|
|
|
} else if (parseMultiField(builder, fieldName, parserContext, propName, propNode)) {
|
|
|
iterator.remove();
|
|
|
}
|
|
@@ -249,10 +344,42 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
|
|
|
public static final class StringFieldType extends MappedFieldType {
|
|
|
|
|
|
- public StringFieldType() {}
|
|
|
+ private boolean fielddata;
|
|
|
+ private double fielddataMinFrequency;
|
|
|
+ private double fielddataMaxFrequency;
|
|
|
+ private int fielddataMinSegmentSize;
|
|
|
+
|
|
|
+ public StringFieldType() {
|
|
|
+ fielddata = true;
|
|
|
+ fielddataMinFrequency = Defaults.FIELDDATA_MIN_FREQUENCY;
|
|
|
+ fielddataMaxFrequency = Defaults.FIELDDATA_MAX_FREQUENCY;
|
|
|
+ fielddataMinSegmentSize = Defaults.FIELDDATA_MIN_SEGMENT_SIZE;
|
|
|
+ }
|
|
|
|
|
|
protected StringFieldType(StringFieldType ref) {
|
|
|
super(ref);
|
|
|
+ this.fielddata = ref.fielddata;
|
|
|
+ this.fielddataMinFrequency = ref.fielddataMinFrequency;
|
|
|
+ this.fielddataMaxFrequency = ref.fielddataMaxFrequency;
|
|
|
+ this.fielddataMinSegmentSize = ref.fielddataMinSegmentSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object o) {
|
|
|
+ if (super.equals(o) == false) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ StringFieldType that = (StringFieldType) o;
|
|
|
+ return fielddata == that.fielddata
|
|
|
+ && fielddataMinFrequency == that.fielddataMinFrequency
|
|
|
+ && fielddataMaxFrequency == that.fielddataMaxFrequency
|
|
|
+ && fielddataMinSegmentSize == that.fielddataMinSegmentSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ return Objects.hash(super.hashCode(), fielddata,
|
|
|
+ fielddataMinFrequency, fielddataMaxFrequency, fielddataMinSegmentSize);
|
|
|
}
|
|
|
|
|
|
public StringFieldType clone() {
|
|
@@ -264,6 +391,67 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
return CONTENT_TYPE;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void checkCompatibility(MappedFieldType other,
|
|
|
+ List<String> conflicts, boolean strict) {
|
|
|
+ super.checkCompatibility(other, conflicts, strict);
|
|
|
+ StringFieldType otherType = (StringFieldType) other;
|
|
|
+ if (strict) {
|
|
|
+ if (fielddata() != otherType.fielddata()) {
|
|
|
+ conflicts.add("mapper [" + name() + "] is used by multiple types. Set update_all_types to true to update [fielddata] "
|
|
|
+ + "across all types.");
|
|
|
+ }
|
|
|
+ if (fielddataMinFrequency() != otherType.fielddataMinFrequency()) {
|
|
|
+ conflicts.add("mapper [" + name() + "] is used by multiple types. Set update_all_types to true to update "
|
|
|
+ + "[fielddata_frequency_filter.min] across all types.");
|
|
|
+ }
|
|
|
+ if (fielddataMaxFrequency() != otherType.fielddataMaxFrequency()) {
|
|
|
+ conflicts.add("mapper [" + name() + "] is used by multiple types. Set update_all_types to true to update "
|
|
|
+ + "[fielddata_frequency_filter.max] across all types.");
|
|
|
+ }
|
|
|
+ if (fielddataMinSegmentSize() != otherType.fielddataMinSegmentSize()) {
|
|
|
+ conflicts.add("mapper [" + name() + "] is used by multiple types. Set update_all_types to true to update "
|
|
|
+ + "[fielddata_frequency_filter.min_segment_size] across all types.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean fielddata() {
|
|
|
+ return fielddata;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFielddata(boolean fielddata) {
|
|
|
+ checkIfFrozen();
|
|
|
+ this.fielddata = fielddata;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double fielddataMinFrequency() {
|
|
|
+ return fielddataMinFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFielddataMinFrequency(double fielddataMinFrequency) {
|
|
|
+ checkIfFrozen();
|
|
|
+ this.fielddataMinFrequency = fielddataMinFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double fielddataMaxFrequency() {
|
|
|
+ return fielddataMaxFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFielddataMaxFrequency(double fielddataMaxFrequency) {
|
|
|
+ checkIfFrozen();
|
|
|
+ this.fielddataMaxFrequency = fielddataMaxFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int fielddataMinSegmentSize() {
|
|
|
+ return fielddataMinSegmentSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFielddataMinSegmentSize(int fielddataMinSegmentSize) {
|
|
|
+ checkIfFrozen();
|
|
|
+ this.fielddataMinSegmentSize = fielddataMinSegmentSize;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String value(Object value) {
|
|
|
if (value == null) {
|
|
@@ -279,13 +467,26 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
}
|
|
|
return termQuery(nullValue(), null);
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IndexFieldData.Builder fielddataBuilder() {
|
|
|
+ if (hasDocValues()) {
|
|
|
+ return new DocValuesIndexFieldData.Builder();
|
|
|
+ } else if (fielddata) {
|
|
|
+ return new PagedBytesIndexFieldData.Builder(fielddataMinFrequency, fielddataMaxFrequency, fielddataMinSegmentSize);
|
|
|
+ } else {
|
|
|
+ throw new IllegalStateException("Fielddata is disabled on analyzed string fields by default. Set fielddata=true on ["
|
|
|
+ + name() + "] in order to load fielddata in memory by uninverting the inverted index. Note that this can however "
|
|
|
+ + "use significant memory.");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private Boolean includeInAll;
|
|
|
private int positionIncrementGap;
|
|
|
private int ignoreAbove;
|
|
|
|
|
|
- protected StringFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
|
|
+ protected StringFieldMapper(String simpleName, StringFieldType fieldType, MappedFieldType defaultFieldType,
|
|
|
int positionIncrementGap, int ignoreAbove,
|
|
|
Settings indexSettings, MultiFields multiFields, CopyTo copyTo) {
|
|
|
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
|
|
@@ -296,6 +497,12 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
if (fieldType.tokenized() && fieldType.indexOptions() != NONE && fieldType().hasDocValues()) {
|
|
|
throw new MapperParsingException("Field [" + fieldType.name() + "] cannot be analyzed and have doc values");
|
|
|
}
|
|
|
+ if (fieldType.hasDocValues() && (
|
|
|
+ fieldType.fielddataMinFrequency() != Defaults.FIELDDATA_MIN_FREQUENCY
|
|
|
+ || fieldType.fielddataMaxFrequency() != Defaults.FIELDDATA_MAX_FREQUENCY
|
|
|
+ || fieldType.fielddataMinSegmentSize() != Defaults.FIELDDATA_MIN_SEGMENT_SIZE)) {
|
|
|
+ throw new MapperParsingException("Field [" + fieldType.name() + "] cannot have doc values and use fielddata filtering");
|
|
|
+ }
|
|
|
this.positionIncrementGap = positionIncrementGap;
|
|
|
this.ignoreAbove = ignoreAbove;
|
|
|
}
|
|
@@ -439,6 +646,11 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public StringFieldType fieldType() {
|
|
|
+ return (StringFieldType) super.fieldType();
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
|
|
super.doXContentBody(builder, includeDefaults, params);
|
|
@@ -460,6 +672,27 @@ public class StringFieldMapper extends FieldMapper implements AllFieldMapper.Inc
|
|
|
if (includeDefaults || ignoreAbove != Defaults.IGNORE_ABOVE) {
|
|
|
builder.field("ignore_above", ignoreAbove);
|
|
|
}
|
|
|
+ if (includeDefaults || fieldType().fielddata() != ((StringFieldType) defaultFieldType).fielddata()) {
|
|
|
+ builder.field("fielddata", fieldType().fielddata());
|
|
|
+ }
|
|
|
+ if (fieldType().fielddata()) {
|
|
|
+ if (includeDefaults
|
|
|
+ || fieldType().fielddataMinFrequency() != Defaults.FIELDDATA_MIN_FREQUENCY
|
|
|
+ || fieldType().fielddataMaxFrequency() != Defaults.FIELDDATA_MAX_FREQUENCY
|
|
|
+ || fieldType().fielddataMinSegmentSize() != Defaults.FIELDDATA_MIN_SEGMENT_SIZE) {
|
|
|
+ builder.startObject("fielddata_frequency_filter");
|
|
|
+ if (includeDefaults || fieldType().fielddataMinFrequency() != Defaults.FIELDDATA_MIN_FREQUENCY) {
|
|
|
+ builder.field("min", fieldType().fielddataMinFrequency());
|
|
|
+ }
|
|
|
+ if (includeDefaults || fieldType().fielddataMaxFrequency() != Defaults.FIELDDATA_MAX_FREQUENCY) {
|
|
|
+ builder.field("max", fieldType().fielddataMaxFrequency());
|
|
|
+ }
|
|
|
+ if (includeDefaults || fieldType().fielddataMinSegmentSize() != Defaults.FIELDDATA_MIN_SEGMENT_SIZE) {
|
|
|
+ builder.field("min_segment_size", fieldType().fielddataMinSegmentSize());
|
|
|
+ }
|
|
|
+ builder.endObject();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|