From 7f1eda4736e851e0855d1dbee1c12bbfeeb96086 Mon Sep 17 00:00:00 2001 From: Jordan Powers Date: Thu, 20 Nov 2025 16:58:19 -0800 Subject: [PATCH 01/14] Add doc_values.cardinality paramter to keyword fields --- .../index/mapper/FieldMapper.java | 80 +++++++++++++++++-- .../index/mapper/KeywordFieldMapper.java | 61 ++++++++++---- .../index/mapper/KeywordFieldMapperTests.java | 24 ++++++ 3 files changed, 141 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index 14c1232696976..cea39b536e838 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -617,7 +617,7 @@ public Builder add(FieldMapper.Builder builder) { if (builder instanceof KeywordFieldMapper.Builder kwd) { if ((kwd.hasNormalizer() == false || kwd.isNormalizerSkipStoreOriginalValue()) - && (kwd.hasDocValues() || kwd.isStored())) { + && (kwd.docValuesParameters().enabled || kwd.isStored())) { hasSyntheticSourceCompatibleKeywordField = true; } } @@ -778,7 +778,7 @@ public interface SerializerCheck { * A configurable parameter for a field mapper * @param the type of the value the parameter holds */ - public static final class Parameter implements Supplier { + public static class Parameter implements Supplier { public final String name; private List deprecatedNames = List.of(); @@ -1405,6 +1405,76 @@ public static Parameter onScriptErrorParam( } } + public static final class DocValuesParameter extends Parameter { + public static final String PARAMETER_NAME = "doc_values"; + + public record Values(boolean enabled, Cardinality cardinality) { + public enum Cardinality { + LOW, + HIGH; + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + } + + public static Values DISABLED = new Values(false, Cardinality.LOW); + } + + public final Parameter cardinalityParameter; + + public DocValuesParameter(Values defaultValue, Function initializer) { + super(PARAMETER_NAME, false, () -> defaultValue, null, initializer, null, Values::toString); + + cardinalityParameter = Parameter.enumParam( + "cardinality", + false, + m -> initializer.apply(m).cardinality, + defaultValue.cardinality, + Values.Cardinality.class + ); + } + + @Override + public void parse(String field, MappingParserContext context, Object value) { + if (value instanceof Boolean valueBool) { + if (valueBool) { + setValue(getDefaultValue()); + } else { + setValue(Values.DISABLED); + } + } else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map valueMap = (Map) value; + cardinalityParameter.parse(field, context, valueMap.get(cardinalityParameter.name)); + + setValue(new Values(true, cardinalityParameter.getValue())); + } else { + throw new IllegalArgumentException("Illegal value [" + value + "] for parameter [" + name + "]"); + } + } + + @Override + public void setValue(Values value) { + super.setValue(value); + cardinalityParameter.setValue(value.cardinality); + } + + protected void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException { + Values value = getValue(); + if (includeDefaults || isConfigured()) { + if (value.enabled == false) { + builder.field(name, false); + } else { + builder.startObject(name); + builder.field(cardinalityParameter.name, value.cardinality); + builder.endObject(); + } + } + } + } + public static final class Conflicts { private final String mapperName; @@ -1499,11 +1569,7 @@ protected final void validate() { @Override public abstract FieldMapper build(MapperBuilderContext context); - protected void addScriptValidation( - Parameter