Add support for filtering mappings fields (#27603)

Add support for filtering fields returned as part of mappings in get index, get mappings, get field mappings and field capabilities API.

Plugins can plug in their own function, which receives the index as argument, and return a predicate which controls whether each field is included or not in the returned output.
This commit is contained in:
Luca Cavanna 2017-12-05 20:31:29 +01:00 committed by GitHub
parent 25ec068aef
commit f4fb4d3bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1253 additions and 144 deletions

View File

@ -36,9 +36,11 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.List;
/**
@ -46,10 +48,15 @@ import java.util.List;
*/
public class TransportGetIndexAction extends TransportClusterInfoAction<GetIndexRequest, GetIndexResponse> {
private final IndicesService indicesService;
@Inject
public TransportGetIndexAction(Settings settings, TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, GetIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, GetIndexRequest::new, indexNameExpressionResolver);
ThreadPool threadPool, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver, IndicesService indicesService) {
super(settings, GetIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, GetIndexRequest::new,
indexNameExpressionResolver);
this.indicesService = indicesService;
}
@Override
@ -60,7 +67,8 @@ public class TransportGetIndexAction extends TransportClusterInfoAction<GetIndex
@Override
protected ClusterBlockException checkBlock(GetIndexRequest request, ClusterState state) {
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ, indexNameExpressionResolver.concreteIndexNames(state, request));
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ,
indexNameExpressionResolver.concreteIndexNames(state, request));
}
@Override
@ -82,8 +90,14 @@ public class TransportGetIndexAction extends TransportClusterInfoAction<GetIndex
switch (feature) {
case MAPPINGS:
if (!doneMappings) {
mappingsResult = state.metaData().findMappings(concreteIndices, request.types());
doneMappings = true;
try {
mappingsResult = state.metaData().findMappings(concreteIndices, request.types(),
indicesService.getFieldFilter());
doneMappings = true;
} catch (IOException e) {
listener.onFailure(e);
return;
}
}
break;
case ALIASES:

View File

@ -29,13 +29,12 @@ import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.routing.ShardsIterator;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.mapper.DocumentFieldMappers;
@ -50,7 +49,10 @@ import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Collections.singletonMap;
@ -69,7 +71,8 @@ public class TransportGetFieldMappingsIndexAction extends TransportSingleShardAc
public TransportGetFieldMappingsIndexAction(Settings settings, ClusterService clusterService, TransportService transportService,
IndicesService indicesService, ThreadPool threadPool, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, ACTION_NAME, threadPool, clusterService, transportService, actionFilters, indexNameExpressionResolver, GetFieldMappingsIndexRequest::new, ThreadPool.Names.MANAGEMENT);
super(settings, ACTION_NAME, threadPool, clusterService, transportService, actionFilters, indexNameExpressionResolver,
GetFieldMappingsIndexRequest::new, ThreadPool.Names.MANAGEMENT);
this.clusterService = clusterService;
this.indicesService = indicesService;
}
@ -90,6 +93,9 @@ public class TransportGetFieldMappingsIndexAction extends TransportSingleShardAc
protected GetFieldMappingsResponse shardOperation(final GetFieldMappingsIndexRequest request, ShardId shardId) {
assert shardId != null;
IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex());
Predicate<String> metadataFieldPredicate = indicesService::isMetaDataField;
Predicate<String> fieldPredicate = metadataFieldPredicate.or(indicesService.getFieldFilter().apply(shardId.getIndexName()));
Collection<String> typeIntersection;
if (request.types().length == 0) {
typeIntersection = indexService.mapperService().types();
@ -104,16 +110,15 @@ public class TransportGetFieldMappingsIndexAction extends TransportSingleShardAc
}
}
MapBuilder<String, Map<String, FieldMappingMetaData>> typeMappings = new MapBuilder<>();
Map<String, Map<String, FieldMappingMetaData>> typeMappings = new HashMap<>();
for (String type : typeIntersection) {
DocumentMapper documentMapper = indexService.mapperService().documentMapper(type);
Map<String, FieldMappingMetaData> fieldMapping = findFieldMappingsByType(documentMapper, request);
Map<String, FieldMappingMetaData> fieldMapping = findFieldMappingsByType(fieldPredicate, documentMapper, request);
if (!fieldMapping.isEmpty()) {
typeMappings.put(type, fieldMapping);
}
}
return new GetFieldMappingsResponse(singletonMap(shardId.getIndexName(), typeMappings.immutableMap()));
return new GetFieldMappingsResponse(singletonMap(shardId.getIndexName(), Collections.unmodifiableMap(typeMappings)));
}
@Override
@ -163,47 +168,50 @@ public class TransportGetFieldMappingsIndexAction extends TransportSingleShardAc
}
};
private Map<String, FieldMappingMetaData> findFieldMappingsByType(DocumentMapper documentMapper, GetFieldMappingsIndexRequest request) {
MapBuilder<String, FieldMappingMetaData> fieldMappings = new MapBuilder<>();
private static Map<String, FieldMappingMetaData> findFieldMappingsByType(Predicate<String> fieldPredicate,
DocumentMapper documentMapper,
GetFieldMappingsIndexRequest request) {
Map<String, FieldMappingMetaData> fieldMappings = new HashMap<>();
final DocumentFieldMappers allFieldMappers = documentMapper.mappers();
for (String field : request.fields()) {
if (Regex.isMatchAllPattern(field)) {
for (FieldMapper fieldMapper : allFieldMappers) {
addFieldMapper(fieldMapper.fieldType().name(), fieldMapper, fieldMappings, request.includeDefaults());
addFieldMapper(fieldPredicate, fieldMapper.fieldType().name(), fieldMapper, fieldMappings, request.includeDefaults());
}
} else if (Regex.isSimpleMatchPattern(field)) {
for (FieldMapper fieldMapper : allFieldMappers) {
if (Regex.simpleMatch(field, fieldMapper.fieldType().name())) {
addFieldMapper(fieldMapper.fieldType().name(), fieldMapper, fieldMappings,
request.includeDefaults());
addFieldMapper(fieldPredicate, fieldMapper.fieldType().name(),
fieldMapper, fieldMappings, request.includeDefaults());
}
}
} else {
// not a pattern
FieldMapper fieldMapper = allFieldMappers.smartNameFieldMapper(field);
if (fieldMapper != null) {
addFieldMapper(field, fieldMapper, fieldMappings, request.includeDefaults());
addFieldMapper(fieldPredicate, field, fieldMapper, fieldMappings, request.includeDefaults());
} else if (request.probablySingleFieldRequest()) {
fieldMappings.put(field, FieldMappingMetaData.NULL);
}
}
}
return fieldMappings.immutableMap();
return Collections.unmodifiableMap(fieldMappings);
}
private void addFieldMapper(String field, FieldMapper fieldMapper, MapBuilder<String, FieldMappingMetaData> fieldMappings, boolean includeDefaults) {
private static void addFieldMapper(Predicate<String> fieldPredicate,
String field, FieldMapper fieldMapper, Map<String, FieldMappingMetaData> fieldMappings,
boolean includeDefaults) {
if (fieldMappings.containsKey(field)) {
return;
}
try {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.startObject();
fieldMapper.toXContent(builder, includeDefaults ? includeDefaultsParams : ToXContent.EMPTY_PARAMS);
builder.endObject();
fieldMappings.put(field, new FieldMappingMetaData(fieldMapper.fieldType().name(), builder.bytes()));
} catch (IOException e) {
throw new ElasticsearchException("failed to serialize XContent of field [" + field + "]", e);
if (fieldPredicate.test(field)) {
try {
BytesReference bytes = XContentHelper.toXContent(fieldMapper, XContentType.JSON,
includeDefaults ? includeDefaultsParams : ToXContent.EMPTY_PARAMS, false);
fieldMappings.put(field, new FieldMappingMetaData(fieldMapper.fieldType().name(), bytes));
} catch (IOException e) {
throw new ElasticsearchException("failed to serialize XContent of field [" + field + "]", e);
}
}
}
}

View File

@ -31,15 +31,23 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
public class TransportGetMappingsAction extends TransportClusterInfoAction<GetMappingsRequest, GetMappingsResponse> {
private final IndicesService indicesService;
@Inject
public TransportGetMappingsAction(Settings settings, TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, GetMappingsAction.NAME, transportService, clusterService, threadPool, actionFilters, GetMappingsRequest::new, indexNameExpressionResolver);
ThreadPool threadPool, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver, IndicesService indicesService) {
super(settings, GetMappingsAction.NAME, transportService, clusterService, threadPool, actionFilters, GetMappingsRequest::new,
indexNameExpressionResolver);
this.indicesService = indicesService;
}
@Override
@ -50,7 +58,8 @@ public class TransportGetMappingsAction extends TransportClusterInfoAction<GetMa
@Override
protected ClusterBlockException checkBlock(GetMappingsRequest request, ClusterState state) {
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ, indexNameExpressionResolver.concreteIndexNames(state, request));
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ,
indexNameExpressionResolver.concreteIndexNames(state, request));
}
@Override
@ -59,11 +68,15 @@ public class TransportGetMappingsAction extends TransportClusterInfoAction<GetMa
}
@Override
protected void doMasterOperation(final GetMappingsRequest request, String[] concreteIndices, final ClusterState state, final ActionListener<GetMappingsResponse> listener) {
protected void doMasterOperation(final GetMappingsRequest request, String[] concreteIndices, final ClusterState state,
final ActionListener<GetMappingsResponse> listener) {
logger.trace("serving getMapping request based on version {}", state.version());
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> result = state.metaData().findMappings(
concreteIndices, request.types()
);
listener.onResponse(new GetMappingsResponse(result));
try {
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> result =
state.metaData().findMappings(concreteIndices, request.types(), indicesService.getFieldFilter());
listener.onResponse(new GetMappingsResponse(result));
} catch (IOException e) {
listener.onFailure(e);
}
}
}

View File

@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public class TransportFieldCapabilitiesIndexAction extends TransportSingleShardAction<FieldCapabilitiesIndexRequest,
FieldCapabilitiesIndexResponse> {
@ -77,12 +78,15 @@ public class TransportFieldCapabilitiesIndexAction extends TransportSingleShardA
for (String field : request.fields()) {
fieldNames.addAll(mapperService.simpleMatchToIndexNames(field));
}
Predicate<String> fieldPredicate = indicesService.getFieldFilter().apply(shardId.getIndexName());
Map<String, FieldCapabilities> responseMap = new HashMap<>();
for (String field : fieldNames) {
MappedFieldType ft = mapperService.fullName(field);
if (ft != null) {
FieldCapabilities fieldCap = new FieldCapabilities(field, ft.typeName(), ft.isSearchable(), ft.isAggregatable());
responseMap.put(field, fieldCap);
if (indicesService.isMetaDataField(field) || fieldPredicate.test(field)) {
responseMap.put(field, fieldCap);
}
}
}
return new FieldCapabilitiesIndexResponse(shardId.getIndexName(), responseMap);

View File

@ -107,15 +107,6 @@ public class MappingMetaData extends AbstractDiffable<MappingMetaData> {
initMappers(withoutType);
}
private MappingMetaData() {
this.type = "";
try {
this.source = new CompressedXContent("{}");
} catch (IOException ex) {
throw new IllegalStateException("Cannot create MappingMetaData prototype", ex);
}
}
private void initMappers(Map<String, Object> withoutType) {
if (withoutType.containsKey("_routing")) {
boolean required = false;
@ -143,13 +134,6 @@ public class MappingMetaData extends AbstractDiffable<MappingMetaData> {
}
}
public MappingMetaData(String type, CompressedXContent source, Routing routing, boolean hasParentField) {
this.type = type;
this.source = source;
this.routing = routing;
this.hasParentField = hasParentField;
}
void updateDefaultMapping(MappingMetaData defaultMapping) {
if (routing == Routing.EMPTY) {
routing = defaultMapping.routing();
@ -250,5 +234,4 @@ public class MappingMetaData extends AbstractDiffable<MappingMetaData> {
public static Diff<MappingMetaData> readDiffFrom(StreamInput in) throws IOException {
return readDiffFrom(MappingMetaData::new, in);
}
}

View File

@ -53,6 +53,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.gateway.MetaDataStateFormat;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
@ -69,6 +70,8 @@ import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
import static org.elasticsearch.common.settings.Settings.writeSettingsToStream;
@ -324,32 +327,38 @@ public class MetaData implements Iterable<IndexMetaData>, Diffable<MetaData>, To
return false;
}
/*
* Finds all mappings for types and concrete indices. Types are expanded to
* include all types that match the glob patterns in the types array. Empty
* types array, null or {"_all"} will be expanded to all types available for
* the given indices.
/**
* Finds all mappings for types and concrete indices. Types are expanded to include all types that match the glob
* patterns in the types array. Empty types array, null or {"_all"} will be expanded to all types available for
* the given indices. Only fields that match the provided field filter will be returned (default is a predicate
* that always returns true, which can be overridden via plugins)
*
* @see MapperPlugin#getFieldFilter()
*
*/
public ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> findMappings(String[] concreteIndices, final String[] types) {
public ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> findMappings(String[] concreteIndices,
final String[] types,
Function<String, Predicate<String>> fieldFilter)
throws IOException {
assert types != null;
assert concreteIndices != null;
if (concreteIndices.length == 0) {
return ImmutableOpenMap.of();
}
boolean isAllTypes = isAllTypes(types);
ImmutableOpenMap.Builder<String, ImmutableOpenMap<String, MappingMetaData>> indexMapBuilder = ImmutableOpenMap.builder();
Iterable<String> intersection = HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
for (String index : intersection) {
IndexMetaData indexMetaData = indices.get(index);
ImmutableOpenMap.Builder<String, MappingMetaData> filteredMappings;
if (isAllTypes(types)) {
indexMapBuilder.put(index, indexMetaData.getMappings()); // No types specified means get it all
Predicate<String> fieldPredicate = fieldFilter.apply(index);
if (isAllTypes) {
indexMapBuilder.put(index, filterFields(indexMetaData.getMappings(), fieldPredicate));
} else {
filteredMappings = ImmutableOpenMap.builder();
ImmutableOpenMap.Builder<String, MappingMetaData> filteredMappings = ImmutableOpenMap.builder();
for (ObjectObjectCursor<String, MappingMetaData> cursor : indexMetaData.getMappings()) {
if (Regex.simpleMatch(types, cursor.key)) {
filteredMappings.put(cursor.key, cursor.value);
filteredMappings.put(cursor.key, filterFields(cursor.value, fieldPredicate));
}
}
if (!filteredMappings.isEmpty()) {
@ -360,6 +369,88 @@ public class MetaData implements Iterable<IndexMetaData>, Diffable<MetaData>, To
return indexMapBuilder.build();
}
private static ImmutableOpenMap<String, MappingMetaData> filterFields(ImmutableOpenMap<String, MappingMetaData> mappings,
Predicate<String> fieldPredicate) throws IOException {
if (fieldPredicate == MapperPlugin.NOOP_FIELD_PREDICATE) {
return mappings;
}
ImmutableOpenMap.Builder<String, MappingMetaData> builder = ImmutableOpenMap.builder(mappings.size());
for (ObjectObjectCursor<String, MappingMetaData> cursor : mappings) {
builder.put(cursor.key, filterFields(cursor.value, fieldPredicate));
}
return builder.build(); // No types specified means return them all
}
@SuppressWarnings("unchecked")
private static MappingMetaData filterFields(MappingMetaData mappingMetaData,
Predicate<String> fieldPredicate) throws IOException {
if (fieldPredicate == MapperPlugin.NOOP_FIELD_PREDICATE) {
return mappingMetaData;
}
Map<String, Object> sourceAsMap = mappingMetaData.getSourceAsMap();
Map<String, Object> properties = (Map<String, Object>)sourceAsMap.get("properties");
if (properties == null || properties.isEmpty()) {
return mappingMetaData;
}
filterFields("", properties, fieldPredicate);
return new MappingMetaData(mappingMetaData.type(), sourceAsMap);
}
@SuppressWarnings("unchecked")
private static boolean filterFields(String currentPath, Map<String, Object> fields, Predicate<String> fieldPredicate) {
Iterator<Map.Entry<String, Object>> entryIterator = fields.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, Object> entry = entryIterator.next();
String newPath = mergePaths(currentPath, entry.getKey());
Object value = entry.getValue();
boolean mayRemove = true;
boolean isMultiField = false;
if (value instanceof Map) {
Map<String, Object> map = (Map<String, Object>) value;
Map<String, Object> properties = (Map<String, Object>)map.get("properties");
if (properties != null) {
mayRemove = filterFields(newPath, properties, fieldPredicate);
} else {
Map<String, Object> subFields = (Map<String, Object>)map.get("fields");
if (subFields != null) {
isMultiField = true;
if (mayRemove = filterFields(newPath, subFields, fieldPredicate)) {
map.remove("fields");
}
}
}
} else {
throw new IllegalStateException("cannot filter mappings, found unknown element of type [" + value.getClass() + "]");
}
//only remove a field if it has no sub-fields left and it has to be excluded
if (fieldPredicate.test(newPath) == false) {
if (mayRemove) {
entryIterator.remove();
} else if (isMultiField) {
//multi fields that should be excluded but hold subfields that don't have to be excluded are converted to objects
Map<String, Object> map = (Map<String, Object>) value;
Map<String, Object> subFields = (Map<String, Object>)map.get("fields");
assert subFields.size() > 0;
map.put("properties", subFields);
map.remove("fields");
map.remove("type");
}
}
}
//return true if the ancestor may be removed, as it has no sub-fields left
return fields.size() == 0;
}
private static String mergePaths(String path, String field) {
if (path.length() == 0) {
return field;
}
return path + "." + field;
}
/**
* Returns all the concrete indices.
*/

View File

@ -74,7 +74,6 @@ public final class ThreadContext implements Closeable, Writeable {
private static final ThreadContextStruct DEFAULT_CONTEXT = new ThreadContextStruct();
private final Map<String, String> defaultHeader;
private final ContextThreadLocal threadLocal;
private boolean isSystemContext;
/**
* Creates a new ThreadContext instance
@ -121,7 +120,6 @@ public final class ThreadContext implements Closeable, Writeable {
return () -> threadLocal.set(context);
}
/**
* Just like {@link #stashContext()} but no default context is set.
* @param preserveResponseHeaders if set to <code>true</code> the response headers of the restore thread will be preserved.

View File

@ -105,6 +105,8 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
Setting.boolSetting("index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT,
Property.Dynamic, Property.IndexScope, Property.Deprecated);
//TODO this needs to be cleaned up: _timestamp and _ttl are not supported anymore, _field_names, _seq_no, _version and _source are
//also missing, not sure if on purpose. See IndicesModule#getMetadataMappers
private static ObjectHashSet<String> META_FIELDS = ObjectHashSet.from(
"_uid", "_id", "_type", "_parent", "_routing", "_index",
"_size", "_timestamp", "_ttl"

View File

@ -24,7 +24,6 @@ import org.elasticsearch.action.admin.indices.rollover.MaxAgeCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
import org.elasticsearch.action.resync.TransportResyncReplicationAction;
import org.elasticsearch.index.shard.PrimaryReplicaSyncer;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
@ -33,12 +32,12 @@ import org.elasticsearch.index.mapper.BooleanFieldMapper;
import org.elasticsearch.index.mapper.CompletionFieldMapper;
import org.elasticsearch.index.mapper.DateFieldMapper;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.IdFieldMapper;
import org.elasticsearch.index.mapper.IndexFieldMapper;
import org.elasticsearch.index.mapper.IpFieldMapper;
import org.elasticsearch.index.mapper.KeywordFieldMapper;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.NumberFieldMapper;
@ -52,6 +51,7 @@ import org.elasticsearch.index.mapper.TypeFieldMapper;
import org.elasticsearch.index.mapper.UidFieldMapper;
import org.elasticsearch.index.mapper.VersionFieldMapper;
import org.elasticsearch.index.seqno.GlobalCheckpointSyncAction;
import org.elasticsearch.index.shard.PrimaryReplicaSyncer;
import org.elasticsearch.indices.cluster.IndicesClusterStateService;
import org.elasticsearch.indices.flush.SyncedFlushService;
import org.elasticsearch.indices.mapper.MapperRegistry;
@ -64,6 +64,9 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Configures classes and services that are shared by indices on each node.
@ -73,7 +76,8 @@ public class IndicesModule extends AbstractModule {
private final MapperRegistry mapperRegistry;
public IndicesModule(List<MapperPlugin> mapperPlugins) {
this.mapperRegistry = new MapperRegistry(getMappers(mapperPlugins), getMetadataMappers(mapperPlugins));
this.mapperRegistry = new MapperRegistry(getMappers(mapperPlugins), getMetadataMappers(mapperPlugins),
getFieldFilter(mapperPlugins));
registerBuiltinWritables();
}
@ -118,23 +122,42 @@ public class IndicesModule extends AbstractModule {
return Collections.unmodifiableMap(mappers);
}
private Map<String, MetadataFieldMapper.TypeParser> getMetadataMappers(List<MapperPlugin> mapperPlugins) {
private static final Map<String, MetadataFieldMapper.TypeParser> builtInMetadataMappers = initBuiltInMetadataMappers();
private static Map<String, MetadataFieldMapper.TypeParser> initBuiltInMetadataMappers() {
Map<String, MetadataFieldMapper.TypeParser> builtInMetadataMappers;
// Use a LinkedHashMap for metadataMappers because iteration order matters
builtInMetadataMappers = new LinkedHashMap<>();
// UID first so it will be the first stored field to load (so will benefit from "fields: []" early termination
builtInMetadataMappers.put(UidFieldMapper.NAME, new UidFieldMapper.TypeParser());
builtInMetadataMappers.put(IdFieldMapper.NAME, new IdFieldMapper.TypeParser());
builtInMetadataMappers.put(RoutingFieldMapper.NAME, new RoutingFieldMapper.TypeParser());
builtInMetadataMappers.put(IndexFieldMapper.NAME, new IndexFieldMapper.TypeParser());
builtInMetadataMappers.put(SourceFieldMapper.NAME, new SourceFieldMapper.TypeParser());
builtInMetadataMappers.put(TypeFieldMapper.NAME, new TypeFieldMapper.TypeParser());
builtInMetadataMappers.put(VersionFieldMapper.NAME, new VersionFieldMapper.TypeParser());
builtInMetadataMappers.put(ParentFieldMapper.NAME, new ParentFieldMapper.TypeParser());
builtInMetadataMappers.put(SeqNoFieldMapper.NAME, new SeqNoFieldMapper.TypeParser());
//_field_names must be added last so that it has a chance to see all the other mappers
builtInMetadataMappers.put(FieldNamesFieldMapper.NAME, new FieldNamesFieldMapper.TypeParser());
return Collections.unmodifiableMap(builtInMetadataMappers);
}
private static Map<String, MetadataFieldMapper.TypeParser> getMetadataMappers(List<MapperPlugin> mapperPlugins) {
Map<String, MetadataFieldMapper.TypeParser> metadataMappers = new LinkedHashMap<>();
// builtin metadata mappers
// UID first so it will be the first stored field to load (so will benefit from "fields: []" early termination
metadataMappers.put(UidFieldMapper.NAME, new UidFieldMapper.TypeParser());
metadataMappers.put(IdFieldMapper.NAME, new IdFieldMapper.TypeParser());
metadataMappers.put(RoutingFieldMapper.NAME, new RoutingFieldMapper.TypeParser());
metadataMappers.put(IndexFieldMapper.NAME, new IndexFieldMapper.TypeParser());
metadataMappers.put(SourceFieldMapper.NAME, new SourceFieldMapper.TypeParser());
metadataMappers.put(TypeFieldMapper.NAME, new TypeFieldMapper.TypeParser());
metadataMappers.put(VersionFieldMapper.NAME, new VersionFieldMapper.TypeParser());
metadataMappers.put(ParentFieldMapper.NAME, new ParentFieldMapper.TypeParser());
metadataMappers.put(SeqNoFieldMapper.NAME, new SeqNoFieldMapper.TypeParser());
// _field_names is not registered here, see below
int i = 0;
Map.Entry<String, MetadataFieldMapper.TypeParser> fieldNamesEntry = null;
for (Map.Entry<String, MetadataFieldMapper.TypeParser> entry : builtInMetadataMappers.entrySet()) {
if (i < builtInMetadataMappers.size() - 1) {
metadataMappers.put(entry.getKey(), entry.getValue());
} else {
assert entry.getKey().equals(FieldNamesFieldMapper.NAME) : "_field_names must be the last registered mapper, order counts";
fieldNamesEntry = entry;
}
i++;
}
assert fieldNamesEntry != null;
for (MapperPlugin mapperPlugin : mapperPlugins) {
for (Map.Entry<String, MetadataFieldMapper.TypeParser> entry : mapperPlugin.getMetadataMappers().entrySet()) {
@ -147,11 +170,49 @@ public class IndicesModule extends AbstractModule {
}
}
// we register _field_names here so that it has a chance to see all other mappers, including from plugins
metadataMappers.put(FieldNamesFieldMapper.NAME, new FieldNamesFieldMapper.TypeParser());
// we register _field_names here so that it has a chance to see all the other mappers, including from plugins
metadataMappers.put(fieldNamesEntry.getKey(), fieldNamesEntry.getValue());
return Collections.unmodifiableMap(metadataMappers);
}
/**
* Returns a set containing all of the builtin metadata fields
*/
public static Set<String> getBuiltInMetaDataFields() {
return builtInMetadataMappers.keySet();
}
private static Function<String, Predicate<String>> getFieldFilter(List<MapperPlugin> mapperPlugins) {
Function<String, Predicate<String>> fieldFilter = MapperPlugin.NOOP_FIELD_FILTER;
for (MapperPlugin mapperPlugin : mapperPlugins) {
fieldFilter = and(fieldFilter, mapperPlugin.getFieldFilter());
}
return fieldFilter;
}
private static Function<String, Predicate<String>> and(Function<String, Predicate<String>> first,
Function<String, Predicate<String>> second) {
//the purpose of this method is to not chain no-op field predicates, so that we can easily find out when no plugins plug in
//a field filter, hence skip the mappings filtering part as a whole, as it requires parsing mappings into a map.
if (first == MapperPlugin.NOOP_FIELD_FILTER) {
return second;
}
if (second == MapperPlugin.NOOP_FIELD_FILTER) {
return first;
}
return index -> {
Predicate<String> firstPredicate = first.apply(index);
Predicate<String> secondPredicate = second.apply(index);
if (firstPredicate == MapperPlugin.NOOP_FIELD_PREDICATE) {
return secondPredicate;
}
if (secondPredicate == MapperPlugin.NOOP_FIELD_PREDICATE) {
return firstPredicate;
}
return firstPredicate.and(secondPredicate);
};
}
@Override
protected void configure() {
bind(IndicesStore.class).asEagerSingleton();

View File

@ -127,7 +127,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -1262,4 +1264,22 @@ public class IndicesService extends AbstractLifecycleComponent
}
}
}
/**
* Returns a function which given an index name, returns a predicate which fields must match in order to be returned by get mappings,
* get index, get field mappings and field capabilities API. Useful to filter the fields that such API return.
* The predicate receives the the field name as input argument. In case multiple plugins register a field filter through
* {@link org.elasticsearch.plugins.MapperPlugin#getFieldFilter()}, only fields that match all the registered filters will be
* returned by get mappings, get index, get field mappings and field capabilities API.
*/
public Function<String, Predicate<String>> getFieldFilter() {
return mapperRegistry.getFieldFilter();
}
/**
* Returns true if the provided field is a registered metadata field (including ones registered via plugins), false otherwise.
*/
public boolean isMetaDataField(String field) {
return mapperRegistry.isMetaDataField(field);
}
}

View File

@ -21,10 +21,13 @@ package org.elasticsearch.indices.mapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.plugins.MapperPlugin;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* A registry for all field mappers.
@ -33,11 +36,14 @@ public final class MapperRegistry {
private final Map<String, Mapper.TypeParser> mapperParsers;
private final Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers;
private final Function<String, Predicate<String>> fieldFilter;
public MapperRegistry(Map<String, Mapper.TypeParser> mapperParsers,
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers) {
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers, Function<String, Predicate<String>> fieldFilter) {
this.mapperParsers = Collections.unmodifiableMap(new LinkedHashMap<>(mapperParsers));
this.metadataMapperParsers = Collections.unmodifiableMap(new LinkedHashMap<>(metadataMapperParsers));
this.fieldFilter = fieldFilter;
}
/**
@ -55,4 +61,22 @@ public final class MapperRegistry {
public Map<String, MetadataFieldMapper.TypeParser> getMetadataMapperParsers() {
return metadataMapperParsers;
}
/**
* Returns true if the provide field is a registered metadata field, false otherwise
*/
public boolean isMetaDataField(String field) {
return getMetadataMapperParsers().containsKey(field);
}
/**
* Returns a function that given an index name, returns a predicate that fields must match in order to be returned by get mappings,
* get index, get field mappings and field capabilities API. Useful to filter the fields that such API return.
* The predicate receives the field name as input arguments. In case multiple plugins register a field filter through
* {@link MapperPlugin#getFieldFilter()}, only fields that match all the registered filters will be returned by get mappings,
* get index, get field mappings and field capabilities API.
*/
public Function<String, Predicate<String>> getFieldFilter() {
return fieldFilter;
}
}

View File

@ -19,12 +19,14 @@
package org.elasticsearch.plugins;
import java.util.Collections;
import java.util.Map;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* An extension point for {@link Plugin} implementations to add custom mappers
*/
@ -32,7 +34,7 @@ public interface MapperPlugin {
/**
* Returns additional mapper implementations added by this plugin.
*
* <p>
* The key of the returned {@link Map} is the unique name for the mapper which will be used
* as the mapping {@code type}, and the value is a {@link Mapper.TypeParser} to parse the
* mapper settings into a {@link Mapper}.
@ -43,7 +45,7 @@ public interface MapperPlugin {
/**
* Returns additional metadata mapper implementations added by this plugin.
*
* <p>
* The key of the returned {@link Map} is the unique name for the metadata mapper, which
* is used in the mapping json to configure the metadata mapper, and the value is a
* {@link MetadataFieldMapper.TypeParser} to parse the mapper settings into a
@ -52,4 +54,25 @@ public interface MapperPlugin {
default Map<String, MetadataFieldMapper.TypeParser> getMetadataMappers() {
return Collections.emptyMap();
}
/**
* Returns a function that given an index name returns a predicate which fields must match in order to be returned by get mappings,
* get index, get field mappings and field capabilities API. Useful to filter the fields that such API return. The predicate receives
* the field name as input argument and should return true to show the field and false to hide it.
*/
default Function<String, Predicate<String>> getFieldFilter() {
return NOOP_FIELD_FILTER;
}
/**
* The default field predicate applied, which doesn't filter anything. That means that by default get mappings, get index
* get field mappings and field capabilities API will return every field that's present in the mappings.
*/
Predicate<String> NOOP_FIELD_PREDICATE = field -> true;
/**
* The default field filter applied, which doesn't filter anything. That means that by default get mappings, get index
* get field mappings and field capabilities API will return every field that's present in the mappings.
*/
Function<String, Predicate<String>> NOOP_FIELD_FILTER = index -> NOOP_FIELD_PREDICATE;
}

View File

@ -22,6 +22,7 @@ import org.elasticsearch.Version;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils;
@ -31,8 +32,8 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testArchiveBrokenIndexSettings() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.emptyList());
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.emptyList());
IndexMetaData src = newIndexMeta("foo", Settings.EMPTY);
IndexMetaData indexMetaData = service.archiveBrokenIndexSettings(src);
assertSame(indexMetaData, src);
@ -59,8 +60,8 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testUpgrade() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.emptyList());
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.emptyList());
IndexMetaData src = newIndexMeta("foo", Settings.builder().put("index.refresh_interval", "-200").build());
assertFalse(service.isUpgraded(src));
src = service.upgradeIndexMetaData(src, Version.CURRENT.minimumIndexCompatibilityVersion());
@ -72,8 +73,8 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testIsUpgraded() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.emptyList());
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.emptyList());
IndexMetaData src = newIndexMeta("foo", Settings.builder().put("index.refresh_interval", "-200").build());
assertFalse(service.isUpgraded(src));
Version version = VersionUtils.randomVersionBetween(random(), VersionUtils.getFirstVersion(), VersionUtils.getPreviousVersion());
@ -85,8 +86,8 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testFailUpgrade() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.emptyList());
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.emptyList());
Version minCompat = Version.CURRENT.minimumIndexCompatibilityVersion();
Version indexUpgraded = VersionUtils.randomVersionBetween(random(), minCompat, VersionUtils.getPreviousVersion(Version.CURRENT));
Version indexCreated = Version.fromString((minCompat.major - 1) + "." + randomInt(5) + "." + randomInt(5));
@ -111,14 +112,13 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testPluginUpgrade() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.singletonList(
indexMetaData -> IndexMetaData.builder(indexMetaData)
.settings(
Settings.builder()
.put(indexMetaData.getSettings())
.put("index.refresh_interval", "10s")
).build()));
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.singletonList(
indexMetaData -> IndexMetaData.builder(indexMetaData).settings(
Settings.builder()
.put(indexMetaData.getSettings())
.put("index.refresh_interval", "10s")
).build()));
IndexMetaData src = newIndexMeta("foo", Settings.builder().put("index.refresh_interval", "200s").build());
assertFalse(service.isUpgraded(src));
src = service.upgradeIndexMetaData(src, Version.CURRENT.minimumIndexCompatibilityVersion());
@ -129,12 +129,12 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
public void testPluginUpgradeFailure() {
MetaDataIndexUpgradeService service = new MetaDataIndexUpgradeService(Settings.EMPTY, xContentRegistry(),
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap()), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
Collections.singletonList(
indexMetaData -> {
throw new IllegalStateException("Cannot upgrade index " + indexMetaData.getIndex().getName());
}
));
new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER),
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, Collections.singletonList(
indexMetaData -> {
throw new IllegalStateException("Cannot upgrade index " + indexMetaData.getIndex().getName());
}
));
IndexMetaData src = newIndexMeta("foo", Settings.EMPTY);
String message = expectThrows(IllegalStateException.class, () -> service.upgradeIndexMetaData(src,
Version.CURRENT.minimumIndexCompatibilityVersion())).getMessage();
@ -150,7 +150,6 @@ public class MetaDataIndexUpgradeServiceTests extends ESTestCase {
.put(IndexMetaData.SETTING_VERSION_UPGRADED, Version.V_5_0_0_beta1)
.put(indexSettings)
.build();
IndexMetaData metaData = IndexMetaData.builder(name).settings(build).build();
return metaData;
return IndexMetaData.builder(name).settings(build).build();
}
}

View File

@ -21,17 +21,21 @@ package org.elasticsearch.cluster.metadata;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.Index;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
@ -231,4 +235,370 @@ public class MetaDataTests extends ESTestCase {
);
assertThat(fromStreamMeta.indexGraveyard(), equalTo(fromStreamMeta.indexGraveyard()));
}
public void testFindMappings() throws IOException {
MetaData metaData = MetaData.builder()
.put(IndexMetaData.builder("index1")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping("doc", FIND_MAPPINGS_TEST_ITEM))
.put(IndexMetaData.builder("index2")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping("doc", FIND_MAPPINGS_TEST_ITEM)).build();
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(Strings.EMPTY_ARRAY,
Strings.EMPTY_ARRAY, MapperPlugin.NOOP_FIELD_FILTER);
assertEquals(0, mappings.size());
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
new String[]{"notfound"}, MapperPlugin.NOOP_FIELD_FILTER);
assertEquals(0, mappings.size());
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
Strings.EMPTY_ARRAY, MapperPlugin.NOOP_FIELD_FILTER);
assertEquals(1, mappings.size());
assertIndexMappingsNotFiltered(mappings, "index1");
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(
new String[]{"index1", "index2"},
new String[]{randomBoolean() ? "doc" : "_all"}, MapperPlugin.NOOP_FIELD_FILTER);
assertEquals(2, mappings.size());
assertIndexMappingsNotFiltered(mappings, "index1");
assertIndexMappingsNotFiltered(mappings, "index2");
}
}
public void testFindMappingsNoOpFilters() throws IOException {
MappingMetaData originalMappingMetaData = new MappingMetaData("doc",
XContentHelper.convertToMap(JsonXContent.jsonXContent, FIND_MAPPINGS_TEST_ITEM, true));
MetaData metaData = MetaData.builder()
.put(IndexMetaData.builder("index1")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping(originalMappingMetaData)).build();
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
randomBoolean() ? Strings.EMPTY_ARRAY : new String[]{"_all"}, MapperPlugin.NOOP_FIELD_FILTER);
ImmutableOpenMap<String, MappingMetaData> index1 = mappings.get("index1");
MappingMetaData mappingMetaData = index1.get("doc");
assertSame(originalMappingMetaData, mappingMetaData);
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
randomBoolean() ? Strings.EMPTY_ARRAY : new String[]{"_all"}, index -> field -> randomBoolean());
ImmutableOpenMap<String, MappingMetaData> index1 = mappings.get("index1");
MappingMetaData mappingMetaData = index1.get("doc");
assertNotSame(originalMappingMetaData, mappingMetaData);
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
new String[]{"doc"}, MapperPlugin.NOOP_FIELD_FILTER);
ImmutableOpenMap<String, MappingMetaData> index1 = mappings.get("index1");
MappingMetaData mappingMetaData = index1.get("doc");
assertSame(originalMappingMetaData, mappingMetaData);
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(new String[]{"index1"},
new String[]{"doc"}, index -> field -> randomBoolean());
ImmutableOpenMap<String, MappingMetaData> index1 = mappings.get("index1");
MappingMetaData mappingMetaData = index1.get("doc");
assertNotSame(originalMappingMetaData, mappingMetaData);
}
}
@SuppressWarnings("unchecked")
public void testFindMappingsWithFilters() throws IOException {
MetaData metaData = MetaData.builder()
.put(IndexMetaData.builder("index1")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping("doc", FIND_MAPPINGS_TEST_ITEM))
.put(IndexMetaData.builder("index2")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping("doc", FIND_MAPPINGS_TEST_ITEM))
.put(IndexMetaData.builder("index3")
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
.putMapping("doc", FIND_MAPPINGS_TEST_ITEM)).build();
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(
new String[]{"index1", "index2", "index3"},
new String[]{"doc"}, index -> {
if (index.equals("index1")) {
return field -> field.startsWith("name.") == false && field.startsWith("properties.key.") == false
&& field.equals("age") == false && field.equals("address.location") == false;
}
if (index.equals("index2")) {
return field -> false;
}
return MapperPlugin.NOOP_FIELD_PREDICATE;
});
assertIndexMappingsNoFields(mappings, "index2");
assertIndexMappingsNotFiltered(mappings, "index3");
ImmutableOpenMap<String, MappingMetaData> index1Mappings = mappings.get("index1");
assertNotNull(index1Mappings);
assertEquals(1, index1Mappings.size());
MappingMetaData docMapping = index1Mappings.get("doc");
assertNotNull(docMapping);
Map<String, Object> sourceAsMap = docMapping.getSourceAsMap();
assertEquals(3, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>) sourceAsMap.get("properties");
assertEquals(6, typeProperties.size());
assertTrue(typeProperties.containsKey("birth"));
assertTrue(typeProperties.containsKey("ip"));
assertTrue(typeProperties.containsKey("suggest"));
Map<String, Object> name = (Map<String, Object>) typeProperties.get("name");
assertNotNull(name);
assertEquals(1, name.size());
Map<String, Object> nameProperties = (Map<String, Object>) name.get("properties");
assertNotNull(nameProperties);
assertEquals(0, nameProperties.size());
Map<String, Object> address = (Map<String, Object>) typeProperties.get("address");
assertNotNull(address);
assertEquals(2, address.size());
assertTrue(address.containsKey("type"));
Map<String, Object> addressProperties = (Map<String, Object>) address.get("properties");
assertNotNull(addressProperties);
assertEquals(2, addressProperties.size());
assertLeafs(addressProperties, "street", "area");
Map<String, Object> properties = (Map<String, Object>) typeProperties.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertTrue(properties.containsKey("type"));
Map<String, Object> propertiesProperties = (Map<String, Object>) properties.get("properties");
assertNotNull(propertiesProperties);
assertEquals(2, propertiesProperties.size());
assertLeafs(propertiesProperties, "key");
assertMultiField(propertiesProperties, "value", "keyword");
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(
new String[]{"index1", "index2" , "index3"},
new String[]{"doc"}, index -> field -> (index.equals("index3") && field.endsWith("keyword")));
assertIndexMappingsNoFields(mappings, "index1");
assertIndexMappingsNoFields(mappings, "index2");
ImmutableOpenMap<String, MappingMetaData> index3 = mappings.get("index3");
assertEquals(1, index3.size());
MappingMetaData mappingMetaData = index3.get("doc");
Map<String, Object> sourceAsMap = mappingMetaData.getSourceAsMap();
assertEquals(3, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>) sourceAsMap.get("properties");
assertNotNull(typeProperties);
assertEquals(1, typeProperties.size());
Map<String, Object> properties = (Map<String, Object>) typeProperties.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertTrue(properties.containsKey("type"));
Map<String, Object> propertiesProperties = (Map<String, Object>) properties.get("properties");
assertNotNull(propertiesProperties);
assertEquals(2, propertiesProperties.size());
Map<String, Object> key = (Map<String, Object>) propertiesProperties.get("key");
assertEquals(1, key.size());
Map<String, Object> keyProperties = (Map<String, Object>) key.get("properties");
assertEquals(1, keyProperties.size());
assertLeafs(keyProperties, "keyword");
Map<String, Object> value = (Map<String, Object>) propertiesProperties.get("value");
assertEquals(1, value.size());
Map<String, Object> valueProperties = (Map<String, Object>) value.get("properties");
assertEquals(1, valueProperties.size());
assertLeafs(valueProperties, "keyword");
}
{
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = metaData.findMappings(
new String[]{"index1", "index2" , "index3"},
new String[]{"doc"}, index -> field -> (index.equals("index2")));
assertIndexMappingsNoFields(mappings, "index1");
assertIndexMappingsNoFields(mappings, "index3");
assertIndexMappingsNotFiltered(mappings, "index2");
}
}
@SuppressWarnings("unchecked")
private static void assertIndexMappingsNoFields(ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings,
String index) {
ImmutableOpenMap<String, MappingMetaData> indexMappings = mappings.get(index);
assertNotNull(indexMappings);
assertEquals(1, indexMappings.size());
MappingMetaData docMapping = indexMappings.get("doc");
assertNotNull(docMapping);
Map<String, Object> sourceAsMap = docMapping.getSourceAsMap();
assertEquals(3, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>) sourceAsMap.get("properties");
assertEquals(0, typeProperties.size());
}
@SuppressWarnings("unchecked")
private static void assertIndexMappingsNotFiltered(ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings,
String index) {
ImmutableOpenMap<String, MappingMetaData> indexMappings = mappings.get(index);
assertNotNull(indexMappings);
assertEquals(1, indexMappings.size());
MappingMetaData docMapping = indexMappings.get("doc");
assertNotNull(docMapping);
Map<String, Object> sourceAsMap = docMapping.getSourceAsMap();
assertEquals(3, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>) sourceAsMap.get("properties");
assertEquals(7, typeProperties.size());
assertTrue(typeProperties.containsKey("birth"));
assertTrue(typeProperties.containsKey("age"));
assertTrue(typeProperties.containsKey("ip"));
assertTrue(typeProperties.containsKey("suggest"));
Map<String, Object> name = (Map<String, Object>) typeProperties.get("name");
assertNotNull(name);
assertEquals(1, name.size());
Map<String, Object> nameProperties = (Map<String, Object>) name.get("properties");
assertNotNull(nameProperties);
assertEquals(2, nameProperties.size());
assertLeafs(nameProperties, "first", "last");
Map<String, Object> address = (Map<String, Object>) typeProperties.get("address");
assertNotNull(address);
assertEquals(2, address.size());
assertTrue(address.containsKey("type"));
Map<String, Object> addressProperties = (Map<String, Object>) address.get("properties");
assertNotNull(addressProperties);
assertEquals(3, addressProperties.size());
assertLeafs(addressProperties, "street", "location", "area");
Map<String, Object> properties = (Map<String, Object>) typeProperties.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertTrue(properties.containsKey("type"));
Map<String, Object> propertiesProperties = (Map<String, Object>) properties.get("properties");
assertNotNull(propertiesProperties);
assertEquals(2, propertiesProperties.size());
assertMultiField(propertiesProperties, "key", "keyword");
assertMultiField(propertiesProperties, "value", "keyword");
}
@SuppressWarnings("unchecked")
public static void assertLeafs(Map<String, Object> properties, String... fields) {
for (String field : fields) {
assertTrue(properties.containsKey(field));
@SuppressWarnings("unchecked")
Map<String, Object> fieldProp = (Map<String, Object>)properties.get(field);
assertNotNull(fieldProp);
assertFalse(fieldProp.containsKey("properties"));
assertFalse(fieldProp.containsKey("fields"));
}
}
public static void assertMultiField(Map<String, Object> properties, String field, String... subFields) {
assertTrue(properties.containsKey(field));
@SuppressWarnings("unchecked")
Map<String, Object> fieldProp = (Map<String, Object>)properties.get(field);
assertNotNull(fieldProp);
assertTrue(fieldProp.containsKey("fields"));
@SuppressWarnings("unchecked")
Map<String, Object> subFieldsDef = (Map<String, Object>) fieldProp.get("fields");
assertLeafs(subFieldsDef, subFields);
}
private static final String FIND_MAPPINGS_TEST_ITEM = "{\n" +
" \"doc\": {\n" +
" \"_routing\": {\n" +
" \"required\":true\n" +
" }," +
" \"_source\": {\n" +
" \"enabled\":false\n" +
" }," +
" \"properties\": {\n" +
" \"name\": {\n" +
" \"properties\": {\n" +
" \"first\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"last\": {\n" +
" \"type\": \"keyword\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"birth\": {\n" +
" \"type\": \"date\"\n" +
" },\n" +
" \"age\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"ip\": {\n" +
" \"type\": \"ip\"\n" +
" },\n" +
" \"suggest\" : {\n" +
" \"type\": \"completion\"\n" +
" },\n" +
" \"address\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"street\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\": {\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"area\": {\n" +
" \"type\": \"geo_shape\", \n" +
" \"tree\": \"quadtree\",\n" +
" \"precision\": \"1m\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"properties\": {\n" +
" \"type\": \"nested\",\n" +
" \"properties\": {\n" +
" \"key\" : {\n" +
" \"type\": \"text\",\n" +
" \"fields\": {\n" +
" \"keyword\" : {\n" +
" \"type\" : \"keyword\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"value\" : {\n" +
" \"type\": \"text\",\n" +
" \"fields\": {\n" +
" \"keyword\" : {\n" +
" \"type\" : \"keyword\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}

View File

@ -39,6 +39,7 @@ import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.similarity.SimilarityService;
import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.IndexSettingsModule;
@ -92,7 +93,7 @@ public class CodecTests extends ESTestCase {
IndexSettings settings = IndexSettingsModule.newIndexSettings("_na", nodeSettings);
SimilarityService similarityService = new SimilarityService(settings, null, Collections.emptyMap());
IndexAnalyzers indexAnalyzers = createTestAnalysis(settings, nodeSettings).indexAnalyzers;
MapperRegistry mapperRegistry = new MapperRegistry(Collections.emptyMap(), Collections.emptyMap());
MapperRegistry mapperRegistry = new MapperRegistry(Collections.emptyMap(), Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER);
MapperService service = new MapperService(settings, indexAnalyzers, xContentRegistry(), similarityService, mapperRegistry,
() -> null);
return new CodecService(service, ESLoggerFactory.getLogger("test"));

View File

@ -31,6 +31,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.test.InternalSettingsPlugin;
@ -60,7 +61,8 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
IndexService indexService = createIndex("test", settings);
MapperRegistry mapperRegistry = new MapperRegistry(
Collections.singletonMap(ExternalMapperPlugin.EXTERNAL, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "foo")),
Collections.singletonMap(ExternalMetadataMapper.CONTENT_TYPE, new ExternalMetadataMapper.TypeParser()));
Collections.singletonMap(ExternalMetadataMapper.CONTENT_TYPE, new ExternalMetadataMapper.TypeParser()),
MapperPlugin.NOOP_FIELD_FILTER);
Supplier<QueryShardContext> queryShardContext = () -> {
return indexService.newQueryShardContext(0, null, () -> { throw new UnsupportedOperationException(); }, null);
@ -111,7 +113,7 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
mapperParsers.put(ExternalMapperPlugin.EXTERNAL, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "foo"));
mapperParsers.put(TextFieldMapper.CONTENT_TYPE, new TextFieldMapper.TypeParser());
mapperParsers.put(KeywordFieldMapper.CONTENT_TYPE, new KeywordFieldMapper.TypeParser());
MapperRegistry mapperRegistry = new MapperRegistry(mapperParsers, Collections.emptyMap());
MapperRegistry mapperRegistry = new MapperRegistry(mapperParsers, Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER);
Supplier<QueryShardContext> queryShardContext = () -> {
return indexService.newQueryShardContext(0, null, () -> { throw new UnsupportedOperationException(); }, null);
@ -177,7 +179,7 @@ public class ExternalFieldMapperTests extends ESSingleNodeTestCase {
mapperParsers.put(ExternalMapperPlugin.EXTERNAL, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "foo"));
mapperParsers.put(ExternalMapperPlugin.EXTERNAL_BIS, new ExternalMapper.TypeParser(ExternalMapperPlugin.EXTERNAL, "bar"));
mapperParsers.put(TextFieldMapper.CONTENT_TYPE, new TextFieldMapper.TypeParser());
MapperRegistry mapperRegistry = new MapperRegistry(mapperParsers, Collections.emptyMap());
MapperRegistry mapperRegistry = new MapperRegistry(mapperParsers, Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER);
Supplier<QueryShardContext> queryShardContext = () -> {
return indexService.newQueryShardContext(0, null, () -> { throw new UnsupportedOperationException(); }, null);

View File

@ -0,0 +1,326 @@
/*
* 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.mapper;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.indices.IndicesModule;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.junit.Before;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.elasticsearch.cluster.metadata.MetaDataTests.assertLeafs;
import static org.elasticsearch.cluster.metadata.MetaDataTests.assertMultiField;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
public class FieldFilterMapperPluginTests extends ESSingleNodeTestCase {
@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
return Collections.singleton(FieldFilterPlugin.class);
}
@Before
public void putMappings() {
assertAcked(client().admin().indices().prepareCreate("index1"));
assertAcked(client().admin().indices().prepareCreate("filtered"));
assertAcked(client().admin().indices().preparePutMapping("index1", "filtered")
.setType("doc").setSource(TEST_ITEM, XContentType.JSON));
}
public void testGetMappings() {
GetMappingsResponse getMappingsResponse = client().admin().indices().prepareGetMappings().get();
assertExpectedMappings(getMappingsResponse.mappings());
}
public void testGetIndex() {
GetIndexResponse getIndexResponse = client().admin().indices().prepareGetIndex()
.setFeatures(GetIndexRequest.Feature.MAPPINGS).get();
assertExpectedMappings(getIndexResponse.mappings());
}
public void testGetFieldMappings() {
GetFieldMappingsResponse getFieldMappingsResponse = client().admin().indices().prepareGetFieldMappings().setFields("*").get();
Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetaData>>> mappings = getFieldMappingsResponse.mappings();
assertEquals(2, mappings.size());
assertFieldMappings(mappings.get("index1"), ALL_FLAT_FIELDS);
assertFieldMappings(mappings.get("filtered"), FILTERED_FLAT_FIELDS);
//double check that submitting the filtered mappings to an unfiltered index leads to the same get field mappings output
//as the one coming from a filtered index with same mappings
GetMappingsResponse getMappingsResponse = client().admin().indices().prepareGetMappings("filtered").get();
ImmutableOpenMap<String, MappingMetaData> filtered = getMappingsResponse.getMappings().get("filtered");
assertAcked(client().admin().indices().prepareCreate("test").addMapping("doc", filtered.get("doc").getSourceAsMap()));
GetFieldMappingsResponse response = client().admin().indices().prepareGetFieldMappings("test").setFields("*").get();
assertEquals(1, response.mappings().size());
assertFieldMappings(response.mappings().get("test"), FILTERED_FLAT_FIELDS);
}
public void testFieldCapabilities() {
FieldCapabilitiesResponse index1 = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("index1")).actionGet();
assertFieldCaps(index1, ALL_FLAT_FIELDS);
FieldCapabilitiesResponse filtered = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("filtered")).actionGet();
assertFieldCaps(filtered, FILTERED_FLAT_FIELDS);
//double check that submitting the filtered mappings to an unfiltered index leads to the same field_caps output
//as the one coming from a filtered index with same mappings
GetMappingsResponse getMappingsResponse = client().admin().indices().prepareGetMappings("filtered").get();
ImmutableOpenMap<String, MappingMetaData> filteredMapping = getMappingsResponse.getMappings().get("filtered");
assertAcked(client().admin().indices().prepareCreate("test").addMapping("doc", filteredMapping.get("doc").getSourceAsMap()));
FieldCapabilitiesResponse test = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("test")).actionGet();
assertFieldCaps(test, FILTERED_FLAT_FIELDS);
}
private static void assertFieldCaps(FieldCapabilitiesResponse fieldCapabilitiesResponse, String[] expectedFields) {
Map<String, Map<String, FieldCapabilities>> responseMap = fieldCapabilitiesResponse.get();
Set<String> builtInMetaDataFields = IndicesModule.getBuiltInMetaDataFields();
for (String field : builtInMetaDataFields) {
Map<String, FieldCapabilities> remove = responseMap.remove(field);
assertNotNull(" expected field [" + field + "] not found", remove);
}
for (String field : expectedFields) {
Map<String, FieldCapabilities> remove = responseMap.remove(field);
assertNotNull(" expected field [" + field + "] not found", remove);
}
assertEquals("Some unexpected fields were returned: " + responseMap.keySet(), 0, responseMap.size());
}
private static void assertFieldMappings(Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetaData>> mappings,
String[] expectedFields) {
assertEquals(1, mappings.size());
Map<String, GetFieldMappingsResponse.FieldMappingMetaData> fields = new HashMap<>(mappings.get("doc"));
Set<String> builtInMetaDataFields = IndicesModule.getBuiltInMetaDataFields();
for (String field : builtInMetaDataFields) {
GetFieldMappingsResponse.FieldMappingMetaData fieldMappingMetaData = fields.remove(field);
assertNotNull(" expected field [" + field + "] not found", fieldMappingMetaData);
}
for (String field : expectedFields) {
GetFieldMappingsResponse.FieldMappingMetaData fieldMappingMetaData = fields.remove(field);
assertNotNull("expected field [" + field + "] not found", fieldMappingMetaData);
}
assertEquals("Some unexpected fields were returned: " + fields.keySet(), 0, fields.size());
}
private void assertExpectedMappings(ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings) {
assertEquals(2, mappings.size());
assertNotFiltered(mappings.get("index1"));
ImmutableOpenMap<String, MappingMetaData> filtered = mappings.get("filtered");
assertFiltered(filtered);
assertMappingsAreValid(filtered.get("doc").getSourceAsMap());
}
private void assertMappingsAreValid(Map<String, Object> sourceAsMap) {
//check that the returned filtered mappings are still valid mappings by submitting them and retrieving them back
assertAcked(client().admin().indices().prepareCreate("test").addMapping("doc", sourceAsMap));
GetMappingsResponse testMappingsResponse = client().admin().indices().prepareGetMappings("test").get();
assertEquals(1, testMappingsResponse.getMappings().size());
//the mappings are returned unfiltered for this index, yet they are the same as the previous ones that were returned filtered
assertFiltered(testMappingsResponse.getMappings().get("test"));
}
@SuppressWarnings("unchecked")
private static void assertFiltered(ImmutableOpenMap<String, MappingMetaData> mappings) {
assertEquals(1, mappings.size());
MappingMetaData mappingMetaData = mappings.get("doc");
assertNotNull(mappingMetaData);
Map<String, Object> sourceAsMap = mappingMetaData.getSourceAsMap();
assertEquals(4, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_meta"));
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>)sourceAsMap.get("properties");
assertEquals(4, typeProperties.size());
Map<String, Object> name = (Map<String, Object>)typeProperties.get("name");
assertEquals(1, name.size());
Map<String, Object> nameProperties = (Map<String, Object>)name.get("properties");
assertEquals(1, nameProperties.size());
assertLeafs(nameProperties, "last_visible");
assertLeafs(typeProperties, "age_visible");
Map<String, Object> address = (Map<String, Object>) typeProperties.get("address");
assertNotNull(address);
assertEquals(1, address.size());
Map<String, Object> addressProperties = (Map<String, Object>) address.get("properties");
assertNotNull(addressProperties);
assertEquals(1, addressProperties.size());
assertLeafs(addressProperties, "area_visible");
Map<String, Object> properties = (Map<String, Object>) typeProperties.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertEquals("nested", properties.get("type"));
Map<String, Object> propertiesProperties = (Map<String, Object>) properties.get("properties");
assertNotNull(propertiesProperties);
assertEquals(2, propertiesProperties.size());
assertLeafs(propertiesProperties, "key_visible");
Map<String, Object> value = (Map<String, Object>) propertiesProperties.get("value");
assertNotNull(value);
assertEquals(1, value.size());
Map<String, Object> valueProperties = (Map<String, Object>) value.get("properties");
assertNotNull(valueProperties);
assertEquals(1, valueProperties.size());
assertLeafs(valueProperties, "keyword_visible");
}
@SuppressWarnings("unchecked")
private static void assertNotFiltered(ImmutableOpenMap<String, MappingMetaData> mappings) {
assertEquals(1, mappings.size());
MappingMetaData mappingMetaData = mappings.get("doc");
assertNotNull(mappingMetaData);
Map<String, Object> sourceAsMap = mappingMetaData.getSourceAsMap();
assertEquals(4, sourceAsMap.size());
assertTrue(sourceAsMap.containsKey("_meta"));
assertTrue(sourceAsMap.containsKey("_routing"));
assertTrue(sourceAsMap.containsKey("_source"));
Map<String, Object> typeProperties = (Map<String, Object>)sourceAsMap.get("properties");
assertEquals(5, typeProperties.size());
Map<String, Object> name = (Map<String, Object>)typeProperties.get("name");
assertEquals(1, name.size());
Map<String, Object> nameProperties = (Map<String, Object>)name.get("properties");
assertEquals(2, nameProperties.size());
assertLeafs(nameProperties, "first", "last_visible");
assertLeafs(typeProperties, "birth", "age_visible");
Map<String, Object> address = (Map<String, Object>) typeProperties.get("address");
assertNotNull(address);
assertEquals(1, address.size());
Map<String, Object> addressProperties = (Map<String, Object>) address.get("properties");
assertNotNull(addressProperties);
assertEquals(3, addressProperties.size());
assertLeafs(addressProperties, "street", "location", "area_visible");
Map<String, Object> properties = (Map<String, Object>) typeProperties.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertTrue(properties.containsKey("type"));
Map<String, Object> propertiesProperties = (Map<String, Object>) properties.get("properties");
assertNotNull(propertiesProperties);
assertEquals(2, propertiesProperties.size());
assertMultiField(propertiesProperties, "key_visible", "keyword");
assertMultiField(propertiesProperties, "value", "keyword_visible");
}
public static class FieldFilterPlugin extends Plugin implements MapperPlugin {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> index.equals("filtered") ? field -> field.endsWith("visible") : MapperPlugin.NOOP_FIELD_PREDICATE;
}
}
private static final String[] ALL_FLAT_FIELDS = new String[]{
"name.first", "name.last_visible", "birth", "age_visible", "address.street", "address.location", "address.area_visible",
"properties.key_visible", "properties.key_visible.keyword", "properties.value", "properties.value.keyword_visible"
};
private static final String[] FILTERED_FLAT_FIELDS = new String[]{
"name.last_visible", "age_visible", "address.area_visible", "properties.key_visible", "properties.value.keyword_visible"
};
private static final String TEST_ITEM = "{\n" +
" \"doc\": {\n" +
" \"_meta\": {\n" +
" \"version\":0.19\n" +
" }," +
" \"_routing\": {\n" +
" \"required\":true\n" +
" }," +
" \"_source\": {\n" +
" \"enabled\":false\n" +
" }," +
" \"properties\": {\n" +
" \"name\": {\n" +
" \"properties\": {\n" +
" \"first\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"last_visible\": {\n" +
" \"type\": \"keyword\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"birth\": {\n" +
" \"type\": \"date\"\n" +
" },\n" +
" \"age_visible\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"address\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"street\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\": {\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"area_visible\": {\n" +
" \"type\": \"geo_shape\", \n" +
" \"tree\": \"quadtree\",\n" +
" \"precision\": \"1m\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"properties\": {\n" +
" \"type\": \"nested\",\n" +
" \"properties\": {\n" +
" \"key_visible\" : {\n" +
" \"type\": \"text\",\n" +
" \"fields\": {\n" +
" \"keyword\" : {\n" +
" \"type\" : \"keyword\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"value\" : {\n" +
" \"type\": \"text\",\n" +
" \"fields\": {\n" +
" \"keyword_visible\" : {\n" +
" \"type\" : \"keyword\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}

View File

@ -19,23 +19,37 @@
package org.elasticsearch.indices;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.IdFieldMapper;
import org.elasticsearch.index.mapper.IndexFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParentFieldMapper;
import org.elasticsearch.index.mapper.RoutingFieldMapper;
import org.elasticsearch.index.mapper.SeqNoFieldMapper;
import org.elasticsearch.index.mapper.SourceFieldMapper;
import org.elasticsearch.index.mapper.TextFieldMapper;
import org.elasticsearch.index.mapper.TypeFieldMapper;
import org.elasticsearch.index.mapper.UidFieldMapper;
import org.elasticsearch.index.mapper.VersionFieldMapper;
import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
public class IndicesModuleTests extends ESTestCase {
@ -59,7 +73,7 @@ public class IndicesModuleTests extends ESTestCase {
}
}
List<MapperPlugin> fakePlugins = Arrays.asList(new MapperPlugin() {
private final List<MapperPlugin> fakePlugins = Arrays.asList(new MapperPlugin() {
@Override
public Map<String, Mapper.TypeParser> getMappers() {
return Collections.singletonMap("fake-mapper", new FakeMapperParser());
@ -70,17 +84,44 @@ public class IndicesModuleTests extends ESTestCase {
}
});
private static String[] EXPECTED_METADATA_FIELDS = new String[]{UidFieldMapper.NAME, IdFieldMapper.NAME, RoutingFieldMapper.NAME,
IndexFieldMapper.NAME, SourceFieldMapper.NAME, TypeFieldMapper.NAME, VersionFieldMapper.NAME, ParentFieldMapper.NAME,
SeqNoFieldMapper.NAME, FieldNamesFieldMapper.NAME};
public void testBuiltinMappers() {
IndicesModule module = new IndicesModule(Collections.emptyList());
assertFalse(module.getMapperRegistry().getMapperParsers().isEmpty());
assertFalse(module.getMapperRegistry().getMetadataMapperParsers().isEmpty());
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers = module.getMapperRegistry().getMetadataMapperParsers();
int i = 0;
for (String field : metadataMapperParsers.keySet()) {
assertEquals(EXPECTED_METADATA_FIELDS[i++], field);
}
}
public void testBuiltinWithPlugins() {
IndicesModule noPluginsModule = new IndicesModule(Collections.emptyList());
IndicesModule module = new IndicesModule(fakePlugins);
MapperRegistry registry = module.getMapperRegistry();
assertThat(registry.getMapperParsers().size(), Matchers.greaterThan(1));
assertThat(registry.getMetadataMapperParsers().size(), Matchers.greaterThan(1));
assertThat(registry.getMapperParsers().size(), greaterThan(noPluginsModule.getMapperRegistry().getMapperParsers().size()));
assertThat(registry.getMetadataMapperParsers().size(),
greaterThan(noPluginsModule.getMapperRegistry().getMetadataMapperParsers().size()));
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers = module.getMapperRegistry().getMetadataMapperParsers();
Iterator<String> iterator = metadataMapperParsers.keySet().iterator();
assertEquals(UidFieldMapper.NAME, iterator.next());
String last = null;
while(iterator.hasNext()) {
last = iterator.next();
}
assertEquals(FieldNamesFieldMapper.NAME, last);
}
public void testGetBuiltInMetaDataFields() {
Set<String> builtInMetaDataFields = IndicesModule.getBuiltInMetaDataFields();
int i = 0;
for (String field : builtInMetaDataFields) {
assertEquals(EXPECTED_METADATA_FIELDS[i++], field);
}
}
public void testDuplicateBuiltinMapper() {
@ -92,7 +133,7 @@ public class IndicesModuleTests extends ESTestCase {
});
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new IndicesModule(plugins));
assertThat(e.getMessage(), Matchers.containsString("already registered"));
assertThat(e.getMessage(), containsString("already registered"));
}
public void testDuplicateOtherPluginMapper() {
@ -105,7 +146,7 @@ public class IndicesModuleTests extends ESTestCase {
List<MapperPlugin> plugins = Arrays.asList(plugin, plugin);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new IndicesModule(plugins));
assertThat(e.getMessage(), Matchers.containsString("already registered"));
assertThat(e.getMessage(), containsString("already registered"));
}
public void testDuplicateBuiltinMetadataMapper() {
@ -117,7 +158,7 @@ public class IndicesModuleTests extends ESTestCase {
});
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new IndicesModule(plugins));
assertThat(e.getMessage(), Matchers.containsString("already registered"));
assertThat(e.getMessage(), containsString("already registered"));
}
public void testDuplicateOtherPluginMetadataMapper() {
@ -130,7 +171,7 @@ public class IndicesModuleTests extends ESTestCase {
List<MapperPlugin> plugins = Arrays.asList(plugin, plugin);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new IndicesModule(plugins));
assertThat(e.getMessage(), Matchers.containsString("already registered"));
assertThat(e.getMessage(), containsString("already registered"));
}
public void testDuplicateFieldNamesMapper() {
@ -142,20 +183,102 @@ public class IndicesModuleTests extends ESTestCase {
});
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new IndicesModule(plugins));
assertThat(e.getMessage(), Matchers.containsString("cannot contain metadata mapper [_field_names]"));
assertThat(e.getMessage(), containsString("cannot contain metadata mapper [_field_names]"));
}
public void testFieldNamesIsLast() {
IndicesModule module = new IndicesModule(Collections.emptyList());
List<String> fieldNames = module.getMapperRegistry().getMetadataMapperParsers().keySet()
.stream().collect(Collectors.toList());
List<String> fieldNames = new ArrayList<>(module.getMapperRegistry().getMetadataMapperParsers().keySet());
assertEquals(FieldNamesFieldMapper.NAME, fieldNames.get(fieldNames.size() - 1));
}
public void testFieldNamesIsLastWithPlugins() {
IndicesModule module = new IndicesModule(fakePlugins);
List<String> fieldNames = module.getMapperRegistry().getMetadataMapperParsers().keySet()
.stream().collect(Collectors.toList());
List<String> fieldNames = new ArrayList<>(module.getMapperRegistry().getMetadataMapperParsers().keySet());
assertEquals(FieldNamesFieldMapper.NAME, fieldNames.get(fieldNames.size() - 1));
}
public void testGetFieldFilter() {
List<MapperPlugin> mapperPlugins = Arrays.asList(
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return MapperPlugin.NOOP_FIELD_FILTER;
}
},
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> index.equals("hidden_index") ? field -> false : MapperPlugin.NOOP_FIELD_PREDICATE;
}
},
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> field -> field.equals("hidden_field") == false;
}
},
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> index.equals("filtered") ? field -> field.equals("visible") : MapperPlugin.NOOP_FIELD_PREDICATE;
}
});
IndicesModule indicesModule = new IndicesModule(mapperPlugins);
MapperRegistry mapperRegistry = indicesModule.getMapperRegistry();
Function<String, Predicate<String>> fieldFilter = mapperRegistry.getFieldFilter();
assertNotSame(MapperPlugin.NOOP_FIELD_FILTER, fieldFilter);
assertFalse(fieldFilter.apply("hidden_index").test(randomAlphaOfLengthBetween(3, 5)));
assertTrue(fieldFilter.apply(randomAlphaOfLengthBetween(3, 5)).test(randomAlphaOfLengthBetween(3, 5)));
assertFalse(fieldFilter.apply(randomAlphaOfLengthBetween(3, 5)).test("hidden_field"));
assertFalse(fieldFilter.apply("filtered").test(randomAlphaOfLengthBetween(3, 5)));
assertFalse(fieldFilter.apply("filtered").test("hidden_field"));
assertTrue(fieldFilter.apply("filtered").test("visible"));
assertFalse(fieldFilter.apply("hidden_index").test("visible"));
assertTrue(fieldFilter.apply(randomAlphaOfLengthBetween(3, 5)).test("visible"));
assertFalse(fieldFilter.apply("hidden_index").test("hidden_field"));
}
public void testDefaultFieldFilterIsNoOp() {
int numPlugins = randomIntBetween(0, 10);
List<MapperPlugin> mapperPlugins = new ArrayList<>(numPlugins);
for (int i = 0; i < numPlugins; i++) {
mapperPlugins.add(new MapperPlugin() {});
}
IndicesModule indicesModule = new IndicesModule(mapperPlugins);
Function<String, Predicate<String>> fieldFilter = indicesModule.getMapperRegistry().getFieldFilter();
assertSame(MapperPlugin.NOOP_FIELD_FILTER, fieldFilter);
}
public void testNoOpFieldPredicate() {
List<MapperPlugin> mapperPlugins = Arrays.asList(
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return MapperPlugin.NOOP_FIELD_FILTER;
}
},
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> index.equals("hidden_index") ? field -> false : MapperPlugin.NOOP_FIELD_PREDICATE;
}
},
new MapperPlugin() {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
return index -> index.equals("filtered") ? field -> field.equals("visible") : MapperPlugin.NOOP_FIELD_PREDICATE;
}
});
IndicesModule indicesModule = new IndicesModule(mapperPlugins);
MapperRegistry mapperRegistry = indicesModule.getMapperRegistry();
Function<String, Predicate<String>> fieldFilter = mapperRegistry.getFieldFilter();
assertSame(MapperPlugin.NOOP_FIELD_PREDICATE, fieldFilter.apply(randomAlphaOfLengthBetween(3, 7)));
assertNotSame(MapperPlugin.NOOP_FIELD_PREDICATE, fieldFilter.apply("hidden_index"));
assertNotSame(MapperPlugin.NOOP_FIELD_PREDICATE, fieldFilter.apply("filtered"));
}
}

View File

@ -110,7 +110,6 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
}
}
@Override
protected boolean resetNodeAfterTest() {
return true;
@ -431,4 +430,12 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
assertThat("index not defined", indexStats.containsKey(index), equalTo(true));
assertThat("unexpected shard stats", indexStats.get(index), equalTo(shardStats));
}
public void testIsMetaDataField() {
IndicesService indicesService = getIndicesService();
assertFalse(indicesService.isMetaDataField(randomAlphaOfLengthBetween(10, 15)));
for (String builtIn : IndicesModule.getBuiltInMetaDataFields()) {
assertTrue(indicesService.isMetaDataField(builtIn));
}
}
}

View File

@ -33,6 +33,7 @@ import org.elasticsearch.index.mapper.ParsedDocument;
import org.elasticsearch.index.mapper.SourceToParse;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.test.InternalSettingsPlugin;
@ -56,7 +57,7 @@ public class Murmur3FieldMapperTests extends ESSingleNodeTestCase {
indexService = createIndex("test");
mapperRegistry = new MapperRegistry(
Collections.singletonMap(Murmur3FieldMapper.CONTENT_TYPE, new Murmur3FieldMapper.TypeParser()),
Collections.emptyMap());
Collections.emptyMap(), MapperPlugin.NOOP_FIELD_FILTER);
Supplier<QueryShardContext> queryShardContext = () -> {
return indexService.newQueryShardContext(0, null, () -> { throw new UnsupportedOperationException(); }, null);
};

View File

@ -0,0 +1,35 @@
/*
* 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.mapper;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import java.util.function.Function;
import java.util.function.Predicate;
public class MockFieldFilterPlugin extends Plugin implements MapperPlugin {
@Override
public Function<String, Predicate<String>> getFieldFilter() {
//this filter doesn't filter any field out, but it's used to exercise the code path executed when the filter is not no-op
return index -> field -> true;
}
}

View File

@ -121,6 +121,7 @@ import org.elasticsearch.index.MockEngineFactoryPlugin;
import org.elasticsearch.index.codec.CodecService;
import org.elasticsearch.index.engine.Segment;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MockFieldFilterPlugin;
import org.elasticsearch.index.seqno.SeqNoStats;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.index.shard.IndexShard;
@ -1906,6 +1907,9 @@ public abstract class ESIntegTestCase extends ESTestCase {
if (randomBoolean()) {
mocks.add(AssertingTransportInterceptor.TestPlugin.class);
}
if (randomBoolean()) {
mocks.add(MockFieldFilterPlugin.class);
}
}
if (addMockTransportService()) {