private void getPrefixTerms( ObjectHashSet<Term> terms, final Term prefix, final IndexReader reader) throws IOException { // SlowCompositeReaderWrapper could be used... but this would merge all terms from each segment // into one terms // instance, which is very expensive. Therefore I think it is better to iterate over each leaf // individually. List<LeafReaderContext> leaves = reader.leaves(); for (LeafReaderContext leaf : leaves) { Terms _terms = leaf.reader().terms(field); if (_terms == null) { continue; } TermsEnum termsEnum = _terms.iterator(); TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(prefix.bytes()); if (TermsEnum.SeekStatus.END == seekStatus) { continue; } for (BytesRef term = termsEnum.term(); term != null; term = termsEnum.next()) { if (!StringHelper.startsWith(term, prefix.bytes())) { break; } terms.add(new Term(field, BytesRef.deepCopyOf(term))); if (terms.size() >= maxExpansions) { return; } } } }
@Override public Query rewrite(IndexReader reader) throws IOException { Query rewritten = super.rewrite(reader); if (rewritten != this) { return rewritten; } if (termArrays.isEmpty()) { return new MatchNoDocsQuery(); } MultiPhraseQuery.Builder query = new MultiPhraseQuery.Builder(); query.setSlop(slop); int sizeMinus1 = termArrays.size() - 1; for (int i = 0; i < sizeMinus1; i++) { query.add(termArrays.get(i), positions.get(i)); } Term[] suffixTerms = termArrays.get(sizeMinus1); int position = positions.get(sizeMinus1); ObjectHashSet<Term> terms = new ObjectHashSet<>(); for (Term term : suffixTerms) { getPrefixTerms(terms, term, reader); if (terms.size() > maxExpansions) { break; } } if (terms.isEmpty()) { return Queries.newMatchNoDocsQuery(); } query.add(terms.toArray(Term.class), position); return query.build(); }
/** All entries to lowercase. */ private static ObjectHashSet<MutableCharArray> toLower(Set<String> input) { ObjectHashSet<MutableCharArray> cloned = new ObjectHashSet<MutableCharArray>(input.size()); for (String entry : input) { char[] chars = entry.toCharArray(); CharArrayUtils.toLowerCaseInPlace(chars); cloned.add(new MutableCharArray(chars)); } return cloned; }
@Override public BytesRef binaryValue() { final byte[] bytes = new byte[points.size() * 16]; int off = 0; for (Iterator<ObjectCursor<GeoPoint>> it = points.iterator(); it.hasNext(); ) { final GeoPoint point = it.next().value; ByteUtils.writeDoubleLE(point.getLat(), bytes, off); ByteUtils.writeDoubleLE(point.getLon(), bytes, off + 8); off += 16; } return new BytesRef(bytes); }
/* * 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. */ public ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> findMappings( String[] concreteIndices, final String[] types) { assert types != null; assert concreteIndices != null; if (concreteIndices.length == 0) { return ImmutableOpenMap.of(); } 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 } else { filteredMappings = ImmutableOpenMap.builder(); for (ObjectObjectCursor<String, MappingMetaData> cursor : indexMetaData.getMappings()) { if (Regex.simpleMatch(types, cursor.key)) { filteredMappings.put(cursor.key, cursor.value); } } if (!filteredMappings.isEmpty()) { indexMapBuilder.put(index, filteredMappings.build()); } } } return indexMapBuilder.build(); }
/** Reload all lexical resources associated with the given key. */ private static HashMap<LanguageCode, ILexicalData> reloadResources( ResourceLookup resourceLookup) { // Load lexical resources. ObjectHashSet<MutableCharArray> mergedStopwords = new ObjectHashSet<>(); ArrayList<Pattern> mergedStoplabels = Lists.newArrayList(); HashMap<LanguageCode, ILexicalData> resourceMap = Maps.newHashMap(); for (LanguageCode languageCode : LanguageCode.values()) { final String isoCode = languageCode.getIsoCode(); ObjectHashSet<MutableCharArray> stopwords = toLower(load(resourceLookup, "stopwords." + isoCode)); ArrayList<Pattern> stoplabels = compile(load(resourceLookup, "stoplabels." + isoCode)); mergedStopwords.addAll(stopwords); mergedStoplabels.addAll(stoplabels); resourceMap.put(languageCode, new DefaultLexicalData(stopwords, stoplabels)); } resourceMap.put(null, new DefaultLexicalData(mergedStopwords, mergedStoplabels)); return resourceMap; }
/** * Finds the specific index aliases that match with the specified aliases directly or partially * via wildcards and that point to the specified concrete indices or match partially with the * indices via wildcards. * * @param aliases The names of the index aliases to find * @param concreteIndices The concrete indexes the index aliases must point to order to be * returned. * @return the found index aliases grouped by index */ public ImmutableOpenMap<String, List<AliasMetaData>> findAliases( final String[] aliases, String[] concreteIndices) { assert aliases != null; assert concreteIndices != null; if (concreteIndices.length == 0) { return ImmutableOpenMap.of(); } boolean matchAllAliases = matchAllAliases(aliases); ImmutableOpenMap.Builder<String, List<AliasMetaData>> mapBuilder = ImmutableOpenMap.builder(); Iterable<String> intersection = HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys()); for (String index : intersection) { IndexMetaData indexMetaData = indices.get(index); List<AliasMetaData> filteredValues = new ArrayList<>(); for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) { AliasMetaData value = cursor.value; if (matchAllAliases || Regex.simpleMatch(aliases, value.alias())) { filteredValues.add(value); } } if (!filteredValues.isEmpty()) { // Make the list order deterministic CollectionUtil.timSort( filteredValues, new Comparator<AliasMetaData>() { @Override public int compare(AliasMetaData o1, AliasMetaData o2) { return o1.alias().compareTo(o2.alias()); } }); mapBuilder.put(index, Collections.unmodifiableList(filteredValues)); } } return mapBuilder.build(); }
/** * Checks if at least one of the specified aliases exists in the specified concrete indices. * Wildcards are supported in the alias names for partial matches. * * @param aliases The names of the index aliases to find * @param concreteIndices The concrete indexes the index aliases must point to order to be * returned. * @return whether at least one of the specified aliases exists in one of the specified concrete * indices. */ public boolean hasAliases(final String[] aliases, String[] concreteIndices) { assert aliases != null; assert concreteIndices != null; if (concreteIndices.length == 0) { return false; } Iterable<String> intersection = HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys()); for (String index : intersection) { IndexMetaData indexMetaData = indices.get(index); List<AliasMetaData> filteredValues = new ArrayList<>(); for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) { AliasMetaData value = cursor.value; if (Regex.simpleMatch(aliases, value.alias())) { filteredValues.add(value); } } if (!filteredValues.isEmpty()) { return true; } } return false; }
public static String[] getAllMetaFields() { return META_FIELDS.toArray(String.class); }
/** @return Whether a field is a metadata field. */ public static boolean isMetadataField(String fieldName) { return META_FIELDS.contains(fieldName); }
public class MapperService extends AbstractIndexComponent { /** The reason why a mapping is being merged. */ public enum MergeReason { /** Create or update a mapping. */ MAPPING_UPDATE, /** * Recovery of an existing mapping, for instance because of a restart, if a shard was moved to a * different node or for administrative purposes. */ MAPPING_RECOVERY; } public static final String DEFAULT_MAPPING = "_default_"; public static final Setting<Long> INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING = Setting.longSetting( "index.mapping.nested_fields.limit", 50L, 0, Property.Dynamic, Property.IndexScope); public static final Setting<Long> INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING = Setting.longSetting( "index.mapping.total_fields.limit", 1000L, 0, Property.Dynamic, Property.IndexScope); public static final Setting<Long> INDEX_MAPPING_DEPTH_LIMIT_SETTING = Setting.longSetting( "index.mapping.depth.limit", 20L, 1, Property.Dynamic, Property.IndexScope); public static final boolean INDEX_MAPPER_DYNAMIC_DEFAULT = true; public static final Setting<Boolean> INDEX_MAPPER_DYNAMIC_SETTING = Setting.boolSetting( "index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT, Property.IndexScope); private static ObjectHashSet<String> META_FIELDS = ObjectHashSet.from( "_uid", "_id", "_type", "_all", "_parent", "_routing", "_index", "_size", "_timestamp", "_ttl"); private final AnalysisService analysisService; /** Will create types automatically if they do not exists in the mapping definition yet */ private final boolean dynamic; private volatile String defaultMappingSource; private volatile Map<String, DocumentMapper> mappers = emptyMap(); private volatile FieldTypeLookup fieldTypes; private volatile Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>(); private boolean hasNested = false; // updated dynamically to true when a nested object is added private final DocumentMapperParser documentParser; private final MapperAnalyzerWrapper indexAnalyzer; private final MapperAnalyzerWrapper searchAnalyzer; private final MapperAnalyzerWrapper searchQuoteAnalyzer; private volatile Map<String, MappedFieldType> unmappedFieldTypes = emptyMap(); private volatile Set<String> parentTypes = emptySet(); final MapperRegistry mapperRegistry; public MapperService( IndexSettings indexSettings, AnalysisService analysisService, SimilarityService similarityService, MapperRegistry mapperRegistry, Supplier<QueryShardContext> queryShardContextSupplier) { super(indexSettings); this.analysisService = analysisService; this.fieldTypes = new FieldTypeLookup(); this.documentParser = new DocumentMapperParser( indexSettings, this, analysisService, similarityService, mapperRegistry, queryShardContextSupplier); this.indexAnalyzer = new MapperAnalyzerWrapper(analysisService.defaultIndexAnalyzer(), p -> p.indexAnalyzer()); this.searchAnalyzer = new MapperAnalyzerWrapper(analysisService.defaultSearchAnalyzer(), p -> p.searchAnalyzer()); this.searchQuoteAnalyzer = new MapperAnalyzerWrapper( analysisService.defaultSearchQuoteAnalyzer(), p -> p.searchQuoteAnalyzer()); this.mapperRegistry = mapperRegistry; this.dynamic = this.indexSettings.getValue(INDEX_MAPPER_DYNAMIC_SETTING); if (index().getName().equals(ScriptService.SCRIPT_INDEX)) { defaultMappingSource = "{" + "\"_default_\": {" + "\"properties\": {" + "\"script\": { \"enabled\": false }," + "\"template\": { \"enabled\": false }" + "}" + "}" + "}"; } else { defaultMappingSource = "{\"_default_\":{}}"; } if (logger.isTraceEnabled()) { logger.trace("using dynamic[{}], default mapping source[{}]", dynamic, defaultMappingSource); } else if (logger.isDebugEnabled()) { logger.debug("using dynamic[{}]", dynamic); } } public boolean hasNested() { return this.hasNested; } /** * returns an immutable iterator over current document mappers. * * @param includingDefaultMapping indicates whether the iterator should contain the {@link * #DEFAULT_MAPPING} document mapper. As is this not really an active type, you would * typically set this to false */ public Iterable<DocumentMapper> docMappers(final boolean includingDefaultMapping) { return () -> { final Collection<DocumentMapper> documentMappers; if (includingDefaultMapping) { documentMappers = mappers.values(); } else { documentMappers = mappers .values() .stream() .filter(mapper -> !DEFAULT_MAPPING.equals(mapper.type())) .collect(Collectors.toList()); } return Collections.unmodifiableCollection(documentMappers).iterator(); }; } public AnalysisService analysisService() { return this.analysisService; } public DocumentMapperParser documentMapperParser() { return this.documentParser; } public DocumentMapper merge( String type, CompressedXContent mappingSource, MergeReason reason, boolean updateAllTypes) { if (DEFAULT_MAPPING.equals(type)) { // verify we can parse it // NOTE: never apply the default here DocumentMapper mapper = documentParser.parse(type, mappingSource); // still add it as a document mapper so we have it registered and, for example, persisted back // into // the cluster meta data if needed, or checked for existence synchronized (this) { mappers = newMapBuilder(mappers).put(type, mapper).map(); } try { defaultMappingSource = mappingSource.string(); } catch (IOException e) { throw new ElasticsearchGenerationException("failed to un-compress", e); } return mapper; } else { synchronized (this) { final boolean applyDefault = // the default was already applied if we are recovering reason != MergeReason.MAPPING_RECOVERY // only apply the default mapping if we don't have the type yet && mappers.containsKey(type) == false; DocumentMapper mergeWith = parse(type, mappingSource, applyDefault); return merge(mergeWith, reason, updateAllTypes); } } } private synchronized DocumentMapper merge( DocumentMapper mapper, MergeReason reason, boolean updateAllTypes) { if (mapper.type().length() == 0) { throw new InvalidTypeNameException("mapping type name is empty"); } if (mapper.type().length() > 255) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] is too long; limit is length 255 but was [" + mapper.type().length() + "]"); } if (mapper.type().charAt(0) == '_') { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] can't start with '_'"); } if (mapper.type().contains("#")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include '#' in it"); } if (mapper.type().contains(",")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include ',' in it"); } if (mapper.type().equals(mapper.parentFieldMapper().type())) { throw new IllegalArgumentException("The [_parent.type] option can't point to the same type"); } if (typeNameStartsWithIllegalDot(mapper)) { throw new IllegalArgumentException( "mapping type name [" + mapper.type() + "] must not start with a '.'"); } // 1. compute the merged DocumentMapper DocumentMapper oldMapper = mappers.get(mapper.type()); DocumentMapper newMapper; if (oldMapper != null) { newMapper = oldMapper.merge(mapper.mapping(), updateAllTypes); } else { newMapper = mapper; } // 2. check basic sanity of the new mapping List<ObjectMapper> objectMappers = new ArrayList<>(); List<FieldMapper> fieldMappers = new ArrayList<>(); Collections.addAll(fieldMappers, newMapper.mapping().metadataMappers); MapperUtils.collect(newMapper.mapping().root(), objectMappers, fieldMappers); checkFieldUniqueness(newMapper.type(), objectMappers, fieldMappers); checkObjectsCompatibility(newMapper.type(), objectMappers, fieldMappers, updateAllTypes); // 3. update lookup data-structures // this will in particular make sure that the merged fields are compatible with other types FieldTypeLookup fieldTypes = this.fieldTypes.copyAndAddAll(newMapper.type(), fieldMappers, updateAllTypes); boolean hasNested = this.hasNested; Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers); for (ObjectMapper objectMapper : objectMappers) { fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper); if (objectMapper.nested().isNested()) { hasNested = true; } } fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers); if (reason == MergeReason.MAPPING_UPDATE) { // this check will only be performed on the master node when there is // a call to the update mapping API. For all other cases like // the master node restoring mappings from disk or data nodes // deserializing cluster state that was sent by the master node, // this check will be skipped. checkNestedFieldsLimit(fullPathObjectMappers); checkTotalFieldsLimit(objectMappers.size() + fieldMappers.size()); checkDepthLimit(fullPathObjectMappers.keySet()); checkPercolatorFieldLimit(fieldTypes); } Set<String> parentTypes = this.parentTypes; if (oldMapper == null && newMapper.parentFieldMapper().active()) { parentTypes = new HashSet<>(parentTypes.size() + 1); parentTypes.addAll(this.parentTypes); parentTypes.add(mapper.parentFieldMapper().type()); parentTypes = Collections.unmodifiableSet(parentTypes); } Map<String, DocumentMapper> mappers = new HashMap<>(this.mappers); mappers.put(newMapper.type(), newMapper); for (Map.Entry<String, DocumentMapper> entry : mappers.entrySet()) { if (entry.getKey().equals(DEFAULT_MAPPING)) { continue; } DocumentMapper m = entry.getValue(); // apply changes to the field types back m = m.updateFieldType(fieldTypes.fullNameToFieldType); entry.setValue(m); } mappers = Collections.unmodifiableMap(mappers); // 4. commit the change this.mappers = mappers; this.fieldTypes = fieldTypes; this.hasNested = hasNested; this.fullPathObjectMappers = fullPathObjectMappers; this.parentTypes = parentTypes; assert assertSerialization(newMapper); assert assertMappersShareSameFieldType(); return newMapper; } private boolean assertMappersShareSameFieldType() { for (DocumentMapper mapper : docMappers(false)) { List<FieldMapper> fieldMappers = new ArrayList<>(); Collections.addAll(fieldMappers, mapper.mapping().metadataMappers); MapperUtils.collect(mapper.root(), new ArrayList<ObjectMapper>(), fieldMappers); for (FieldMapper fieldMapper : fieldMappers) { assert fieldMapper.fieldType() == fieldTypes.get(fieldMapper.name()) : fieldMapper.name(); } } return true; } private boolean typeNameStartsWithIllegalDot(DocumentMapper mapper) { boolean legacyIndex = getIndexSettings().getIndexVersionCreated().before(Version.V_5_0_0_alpha1); if (legacyIndex) { return mapper.type().startsWith(".") && !PercolatorFieldMapper.LEGACY_TYPE_NAME.equals(mapper.type()); } else { return mapper.type().startsWith("."); } } private boolean assertSerialization(DocumentMapper mapper) { // capture the source now, it may change due to concurrent parsing final CompressedXContent mappingSource = mapper.mappingSource(); DocumentMapper newMapper = parse(mapper.type(), mappingSource, false); if (newMapper.mappingSource().equals(mappingSource) == false) { throw new IllegalStateException( "DocumentMapper serialization result is different from source. \n--> Source [" + mappingSource + "]\n--> Result [" + newMapper.mappingSource() + "]"); } return true; } private void checkFieldUniqueness( String type, Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers) { assert Thread.holdsLock(this); // first check within mapping final Set<String> objectFullNames = new HashSet<>(); for (ObjectMapper objectMapper : objectMappers) { final String fullPath = objectMapper.fullPath(); if (objectFullNames.add(fullPath) == false) { throw new IllegalArgumentException( "Object mapper [" + fullPath + "] is defined twice in mapping for type [" + type + "]"); } } final Set<String> fieldNames = new HashSet<>(); for (FieldMapper fieldMapper : fieldMappers) { final String name = fieldMapper.name(); if (objectFullNames.contains(name)) { throw new IllegalArgumentException( "Field [" + name + "] is defined both as an object and a field in [" + type + "]"); } else if (fieldNames.add(name) == false) { throw new IllegalArgumentException( "Field [" + name + "] is defined twice in [" + type + "]"); } } // then check other types for (String fieldName : fieldNames) { if (fullPathObjectMappers.containsKey(fieldName)) { throw new IllegalArgumentException( "[" + fieldName + "] is defined as a field in mapping [" + type + "] but this name is already used for an object in other types"); } } for (String objectPath : objectFullNames) { if (fieldTypes.get(objectPath) != null) { throw new IllegalArgumentException( "[" + objectPath + "] is defined as an object in mapping [" + type + "] but this name is already used for a field in other types"); } } } private void checkObjectsCompatibility( String type, Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers, boolean updateAllTypes) { assert Thread.holdsLock(this); for (ObjectMapper newObjectMapper : objectMappers) { ObjectMapper existingObjectMapper = fullPathObjectMappers.get(newObjectMapper.fullPath()); if (existingObjectMapper != null) { // simulate a merge and ignore the result, we are just interested // in exceptions here existingObjectMapper.merge(newObjectMapper, updateAllTypes); } } } private void checkNestedFieldsLimit(Map<String, ObjectMapper> fullPathObjectMappers) { long allowedNestedFields = indexSettings.getValue(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING); long actualNestedFields = 0; for (ObjectMapper objectMapper : fullPathObjectMappers.values()) { if (objectMapper.nested().isNested()) { actualNestedFields++; } } if (actualNestedFields > allowedNestedFields) { throw new IllegalArgumentException( "Limit of nested fields [" + allowedNestedFields + "] in index [" + index().getName() + "] has been exceeded"); } } private void checkTotalFieldsLimit(long totalMappers) { long allowedTotalFields = indexSettings.getValue(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING); if (allowedTotalFields < totalMappers) { throw new IllegalArgumentException( "Limit of total fields [" + allowedTotalFields + "] in index [" + index().getName() + "] has been exceeded"); } } private void checkDepthLimit(Collection<String> objectPaths) { final long maxDepth = indexSettings.getValue(INDEX_MAPPING_DEPTH_LIMIT_SETTING); for (String objectPath : objectPaths) { checkDepthLimit(objectPath, maxDepth); } } private void checkDepthLimit(String objectPath, long maxDepth) { int numDots = 0; for (int i = 0; i < objectPath.length(); ++i) { if (objectPath.charAt(i) == '.') { numDots += 1; } } final int depth = numDots + 2; if (depth > maxDepth) { throw new IllegalArgumentException( "Limit of mapping depth [" + maxDepth + "] in index [" + index().getName() + "] has been exceeded due to object field [" + objectPath + "]"); } } /** * We only allow upto 1 percolator field per index. * * <p>Reasoning here is that the PercolatorQueryCache only supports a single document having a * percolator query. Also specifying multiple queries per document feels like an anti pattern */ private void checkPercolatorFieldLimit(Iterable<MappedFieldType> fieldTypes) { List<String> percolatorFieldTypes = new ArrayList<>(); for (MappedFieldType fieldType : fieldTypes) { if (fieldType instanceof PercolatorFieldMapper.PercolatorFieldType) { percolatorFieldTypes.add(fieldType.name()); } } if (percolatorFieldTypes.size() > 1) { throw new IllegalArgumentException( "Up to one percolator field type is allowed per index, " + "found the following percolator fields [" + percolatorFieldTypes + "]"); } } public DocumentMapper parse( String mappingType, CompressedXContent mappingSource, boolean applyDefault) throws MapperParsingException { return documentParser.parse( mappingType, mappingSource, applyDefault ? defaultMappingSource : null); } public boolean hasMapping(String mappingType) { return mappers.containsKey(mappingType); } /** * Return the set of concrete types that have a mapping. NOTE: this does not return the default * mapping. */ public Collection<String> types() { final Set<String> types = new HashSet<>(mappers.keySet()); types.remove(DEFAULT_MAPPING); return Collections.unmodifiableSet(types); } /** * Return the {@link DocumentMapper} for the given type. By using the special {@value * #DEFAULT_MAPPING} type, you can get a {@link DocumentMapper} for the default mapping. */ public DocumentMapper documentMapper(String type) { return mappers.get(type); } /** * Returns the document mapper created, including a mapping update if the type has been * dynamically created. */ public DocumentMapperForType documentMapperWithAutoCreate(String type) { DocumentMapper mapper = mappers.get(type); if (mapper != null) { return new DocumentMapperForType(mapper, null); } if (!dynamic) { throw new TypeMissingException( index(), type, "trying to auto create mapping, but dynamic mapping is disabled"); } mapper = parse(type, null, true); return new DocumentMapperForType(mapper, mapper.mapping()); } /** * Returns the {@link MappedFieldType} for the give fullName. * * <p>If multiple types have fields with the same full name, the first is returned. */ public MappedFieldType fullName(String fullName) { return fieldTypes.get(fullName); } /** * Returns all the fields that match the given pattern. If the pattern is prefixed with a type * then the fields will be returned with a type prefix. */ public Collection<String> simpleMatchToIndexNames(String pattern) { if (Regex.isSimpleMatchPattern(pattern) == false) { // no wildcards return Collections.singletonList(pattern); } return fieldTypes.simpleMatchToFullName(pattern); } public ObjectMapper getObjectMapper(String name) { return fullPathObjectMappers.get(name); } /** * Given a type (eg. long, string, ...), return an anonymous field mapper that can be used for * search operations. */ public MappedFieldType unmappedFieldType(String type) { if (type.equals("string")) { deprecationLogger.deprecated( "[unmapped_type:string] should be replaced with [unmapped_type:keyword]"); type = "keyword"; } MappedFieldType fieldType = unmappedFieldTypes.get(type); if (fieldType == null) { final Mapper.TypeParser.ParserContext parserContext = documentMapperParser().parserContext(type); Mapper.TypeParser typeParser = parserContext.typeParser(type); if (typeParser == null) { throw new IllegalArgumentException("No mapper found for type [" + type + "]"); } final Mapper.Builder<?, ?> builder = typeParser.parse("__anonymous_" + type, emptyMap(), parserContext); final BuilderContext builderContext = new BuilderContext(indexSettings.getSettings(), new ContentPath(1)); fieldType = ((FieldMapper) builder.build(builderContext)).fieldType(); // There is no need to synchronize writes here. In the case of concurrent access, we could // just // compute some mappers several times, which is not a big deal Map<String, MappedFieldType> newUnmappedFieldTypes = new HashMap<>(); newUnmappedFieldTypes.putAll(unmappedFieldTypes); newUnmappedFieldTypes.put(type, fieldType); unmappedFieldTypes = unmodifiableMap(newUnmappedFieldTypes); } return fieldType; } public Analyzer indexAnalyzer() { return this.indexAnalyzer; } public Analyzer searchAnalyzer() { return this.searchAnalyzer; } public Analyzer searchQuoteAnalyzer() { return this.searchQuoteAnalyzer; } public Set<String> getParentTypes() { return parentTypes; } /** @return Whether a field is a metadata field. */ public static boolean isMetadataField(String fieldName) { return META_FIELDS.contains(fieldName); } public static String[] getAllMetaFields() { return META_FIELDS.toArray(String.class); } /** An analyzer wrapper that can lookup fields within the index mappings */ final class MapperAnalyzerWrapper extends DelegatingAnalyzerWrapper { private final Analyzer defaultAnalyzer; private final Function<MappedFieldType, Analyzer> extractAnalyzer; MapperAnalyzerWrapper( Analyzer defaultAnalyzer, Function<MappedFieldType, Analyzer> extractAnalyzer) { super(Analyzer.PER_FIELD_REUSE_STRATEGY); this.defaultAnalyzer = defaultAnalyzer; this.extractAnalyzer = extractAnalyzer; } @Override protected Analyzer getWrappedAnalyzer(String fieldName) { MappedFieldType fieldType = fullName(fieldName); if (fieldType != null) { Analyzer analyzer = extractAnalyzer.apply(fieldType); if (analyzer != null) { return analyzer; } } return defaultAnalyzer; } } }
public void add(double lat, double lon) { points.add(new GeoPoint(lat, lon)); }
public CustomGeoPointDocValuesField(String name, double lat, double lon) { super(name); points = new ObjectHashSet<>(2); points.add(new GeoPoint(lat, lon)); }
public class MapperService extends AbstractIndexComponent implements Closeable { public static final String DEFAULT_MAPPING = "_default_"; private static ObjectHashSet<String> META_FIELDS = ObjectHashSet.from( "_uid", "_id", "_type", "_all", "_parent", "_routing", "_index", "_size", "_timestamp", "_ttl"); private static final Function<MappedFieldType, Analyzer> INDEX_ANALYZER_EXTRACTOR = new Function<MappedFieldType, Analyzer>() { public Analyzer apply(MappedFieldType fieldType) { return fieldType.indexAnalyzer(); } }; private static final Function<MappedFieldType, Analyzer> SEARCH_ANALYZER_EXTRACTOR = new Function<MappedFieldType, Analyzer>() { public Analyzer apply(MappedFieldType fieldType) { return fieldType.searchAnalyzer(); } }; private static final Function<MappedFieldType, Analyzer> SEARCH_QUOTE_ANALYZER_EXTRACTOR = new Function<MappedFieldType, Analyzer>() { public Analyzer apply(MappedFieldType fieldType) { return fieldType.searchQuoteAnalyzer(); } }; private final AnalysisService analysisService; /** Will create types automatically if they do not exists in the mapping definition yet */ private final boolean dynamic; private volatile String defaultMappingSource; private volatile String defaultPercolatorMappingSource; private volatile Map<String, DocumentMapper> mappers = ImmutableMap.of(); // A lock for mappings: modifications (put mapping) need to be performed // under the write lock and read operations (document parsing) need to be // performed under the read lock final ReentrantReadWriteLock mappingLock = new ReentrantReadWriteLock(); private final ReleasableLock mappingWriteLock = new ReleasableLock(mappingLock.writeLock()); private volatile FieldTypeLookup fieldTypes; private volatile ImmutableOpenMap<String, ObjectMapper> fullPathObjectMappers = ImmutableOpenMap.of(); private boolean hasNested = false; // updated dynamically to true when a nested object is added private final DocumentMapperParser documentParser; private final MapperAnalyzerWrapper indexAnalyzer; private final MapperAnalyzerWrapper searchAnalyzer; private final MapperAnalyzerWrapper searchQuoteAnalyzer; private final List<DocumentTypeListener> typeListeners = new CopyOnWriteArrayList<>(); private volatile ImmutableMap<String, MappedFieldType> unmappedFieldTypes = ImmutableMap.of(); private volatile ImmutableSet<String> parentTypes = ImmutableSet.of(); @Inject public MapperService( Index index, @IndexSettings Settings indexSettings, AnalysisService analysisService, SimilarityLookupService similarityLookupService, ScriptService scriptService) { super(index, indexSettings); this.analysisService = analysisService; this.fieldTypes = new FieldTypeLookup(); this.documentParser = new DocumentMapperParser( indexSettings, this, analysisService, similarityLookupService, scriptService); this.indexAnalyzer = new MapperAnalyzerWrapper(analysisService.defaultIndexAnalyzer(), INDEX_ANALYZER_EXTRACTOR); this.searchAnalyzer = new MapperAnalyzerWrapper( analysisService.defaultSearchAnalyzer(), SEARCH_ANALYZER_EXTRACTOR); this.searchQuoteAnalyzer = new MapperAnalyzerWrapper( analysisService.defaultSearchQuoteAnalyzer(), SEARCH_QUOTE_ANALYZER_EXTRACTOR); this.dynamic = indexSettings.getAsBoolean("index.mapper.dynamic", true); defaultPercolatorMappingSource = "{\n" + "\"_default_\":{\n" + "\"properties\" : {\n" + "\"query\" : {\n" + "\"type\" : \"object\",\n" + "\"enabled\" : false\n" + "}\n" + "}\n" + "}\n" + "}"; if (index.getName().equals(ScriptService.SCRIPT_INDEX)) { defaultMappingSource = "{" + "\"_default_\": {" + "\"properties\": {" + "\"script\": { \"enabled\": false }," + "\"template\": { \"enabled\": false }" + "}" + "}" + "}"; } else { defaultMappingSource = "{\"_default_\":{}}"; } if (logger.isTraceEnabled()) { logger.trace( "using dynamic[{}], default mapping source[{}], default percolator mapping source[{}]", dynamic, defaultMappingSource, defaultPercolatorMappingSource); } else if (logger.isDebugEnabled()) { logger.debug("using dynamic[{}]", dynamic); } } public void close() { for (DocumentMapper documentMapper : mappers.values()) { documentMapper.close(); } } public boolean hasNested() { return this.hasNested; } /** * returns an immutable iterator over current document mappers. * * @param includingDefaultMapping indicates whether the iterator should contain the {@link * #DEFAULT_MAPPING} document mapper. As is this not really an active type, you would * typically set this to false */ public Iterable<DocumentMapper> docMappers(final boolean includingDefaultMapping) { return new Iterable<DocumentMapper>() { @Override public Iterator<DocumentMapper> iterator() { final Iterator<DocumentMapper> iterator; if (includingDefaultMapping) { iterator = mappers.values().iterator(); } else { iterator = Iterators.filter(mappers.values().iterator(), NOT_A_DEFAULT_DOC_MAPPER); } return Iterators.unmodifiableIterator(iterator); } }; } private static final Predicate<DocumentMapper> NOT_A_DEFAULT_DOC_MAPPER = new Predicate<DocumentMapper>() { @Override public boolean apply(DocumentMapper input) { return !DEFAULT_MAPPING.equals(input.type()); } }; public AnalysisService analysisService() { return this.analysisService; } public DocumentMapperParser documentMapperParser() { return this.documentParser; } public void addTypeListener(DocumentTypeListener listener) { typeListeners.add(listener); } public void removeTypeListener(DocumentTypeListener listener) { typeListeners.remove(listener); } public DocumentMapper merge( String type, CompressedXContent mappingSource, boolean applyDefault, boolean updateAllTypes) { if (DEFAULT_MAPPING.equals(type)) { // verify we can parse it DocumentMapper mapper = documentParser.parseCompressed(type, mappingSource); // still add it as a document mapper so we have it registered and, for example, persisted back // into // the cluster meta data if needed, or checked for existence try (ReleasableLock lock = mappingWriteLock.acquire()) { mappers = newMapBuilder(mappers).put(type, mapper).map(); } try { defaultMappingSource = mappingSource.string(); } catch (IOException e) { throw new ElasticsearchGenerationException("failed to un-compress", e); } return mapper; } else { return merge(parse(type, mappingSource, applyDefault), updateAllTypes); } } // never expose this to the outside world, we need to reparse the doc mapper so we get fresh // instances of field mappers to properly remove existing doc mapper private DocumentMapper merge(DocumentMapper mapper, boolean updateAllTypes) { try (ReleasableLock lock = mappingWriteLock.acquire()) { if (mapper.type().length() == 0) { throw new InvalidTypeNameException("mapping type name is empty"); } if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1) && mapper.type().length() > 255) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] is too long; limit is length 255 but was [" + mapper.type().length() + "]"); } if (mapper.type().charAt(0) == '_') { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] can't start with '_'"); } if (mapper.type().contains("#")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include '#' in it"); } if (mapper.type().contains(",")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include ',' in it"); } if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1) && mapper.type().equals(mapper.parentFieldMapper().type())) { throw new IllegalArgumentException( "The [_parent.type] option can't point to the same type"); } if (typeNameStartsWithIllegalDot(mapper)) { if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)) { throw new IllegalArgumentException( "mapping type name [" + mapper.type() + "] must not start with a '.'"); } else { logger.warn( "Type [{}] starts with a '.', it is recommended not to start a type name with a '.'", mapper.type()); } } // we can add new field/object mappers while the old ones are there // since we get new instances of those, and when we remove, we remove // by instance equality DocumentMapper oldMapper = mappers.get(mapper.type()); if (oldMapper != null) { MergeResult result = oldMapper.merge(mapper.mapping(), false, updateAllTypes); if (result.hasConflicts()) { // TODO: What should we do??? if (logger.isDebugEnabled()) { logger.debug( "merging mapping for type [{}] resulted in conflicts: [{}]", mapper.type(), Arrays.toString(result.buildConflicts())); } } return oldMapper; } else { List<ObjectMapper> newObjectMappers = new ArrayList<>(); List<FieldMapper> newFieldMappers = new ArrayList<>(); for (MetadataFieldMapper metadataMapper : mapper.mapping().metadataMappers) { newFieldMappers.add(metadataMapper); } MapperUtils.collect(mapper.mapping().root, newObjectMappers, newFieldMappers); checkNewMappersCompatibility(newObjectMappers, newFieldMappers, updateAllTypes); addMappers(newObjectMappers, newFieldMappers); for (DocumentTypeListener typeListener : typeListeners) { typeListener.beforeCreate(mapper); } mappers = newMapBuilder(mappers).put(mapper.type(), mapper).map(); if (mapper.parentFieldMapper().active()) { ImmutableSet.Builder<String> parentTypesCopy = ImmutableSet.builder(); parentTypesCopy.addAll(parentTypes); parentTypesCopy.add(mapper.parentFieldMapper().type()); parentTypes = parentTypesCopy.build(); } assert assertSerialization(mapper); return mapper; } } } private boolean typeNameStartsWithIllegalDot(DocumentMapper mapper) { return mapper.type().startsWith(".") && !PercolatorService.TYPE_NAME.equals(mapper.type()); } private boolean assertSerialization(DocumentMapper mapper) { // capture the source now, it may change due to concurrent parsing final CompressedXContent mappingSource = mapper.mappingSource(); DocumentMapper newMapper = parse(mapper.type(), mappingSource, false); if (newMapper.mappingSource().equals(mappingSource) == false) { throw new IllegalStateException( "DocumentMapper serialization result is different from source. \n--> Source [" + mappingSource + "]\n--> Result [" + newMapper.mappingSource() + "]"); } return true; } protected void checkNewMappersCompatibility( Collection<ObjectMapper> newObjectMappers, Collection<FieldMapper> newFieldMappers, boolean updateAllTypes) { assert mappingLock.isWriteLockedByCurrentThread(); for (ObjectMapper newObjectMapper : newObjectMappers) { ObjectMapper existingObjectMapper = fullPathObjectMappers.get(newObjectMapper.fullPath()); if (existingObjectMapper != null) { MergeResult result = new MergeResult(true, updateAllTypes); existingObjectMapper.merge(newObjectMapper, result); if (result.hasConflicts()) { throw new IllegalArgumentException( "Mapper for [" + newObjectMapper.fullPath() + "] conflicts with existing mapping in other types" + Arrays.toString(result.buildConflicts())); } } } fieldTypes.checkCompatibility(newFieldMappers, updateAllTypes); } protected void addMappers( Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers) { assert mappingLock.isWriteLockedByCurrentThread(); ImmutableOpenMap.Builder<String, ObjectMapper> fullPathObjectMappers = ImmutableOpenMap.builder(this.fullPathObjectMappers); for (ObjectMapper objectMapper : objectMappers) { fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper); if (objectMapper.nested().isNested()) { hasNested = true; } } this.fullPathObjectMappers = fullPathObjectMappers.build(); this.fieldTypes = this.fieldTypes.copyAndAddAll(fieldMappers); } public DocumentMapper parse( String mappingType, CompressedXContent mappingSource, boolean applyDefault) throws MapperParsingException { String defaultMappingSource; if (PercolatorService.TYPE_NAME.equals(mappingType)) { defaultMappingSource = this.defaultPercolatorMappingSource; } else { defaultMappingSource = this.defaultMappingSource; } return documentParser.parseCompressed( mappingType, mappingSource, applyDefault ? defaultMappingSource : null); } public boolean hasMapping(String mappingType) { return mappers.containsKey(mappingType); } public Collection<String> types() { return mappers.keySet(); } public DocumentMapper documentMapper(String type) { return mappers.get(type); } /** * Returns the document mapper created, including a mapping update if the type has been * dynamically created. */ public DocumentMapperForType documentMapperWithAutoCreate(String type) { DocumentMapper mapper = mappers.get(type); if (mapper != null) { return new DocumentMapperForType(mapper, null); } if (!dynamic) { throw new TypeMissingException( index, type, "trying to auto create mapping, but dynamic mapping is disabled"); } mapper = parse(type, null, true); return new DocumentMapperForType(mapper, mapper.mapping()); } /** * A filter for search. If a filter is required, will return it, otherwise, will return * <tt>null</tt>. */ @Nullable public Query searchFilter(String... types) { boolean filterPercolateType = hasMapping(PercolatorService.TYPE_NAME); if (types != null && filterPercolateType) { for (String type : types) { if (PercolatorService.TYPE_NAME.equals(type)) { filterPercolateType = false; break; } } } Query percolatorType = null; if (filterPercolateType) { percolatorType = documentMapper(PercolatorService.TYPE_NAME).typeFilter(); } if (types == null || types.length == 0) { if (hasNested && filterPercolateType) { BooleanQuery bq = new BooleanQuery(); bq.add(percolatorType, Occur.MUST_NOT); bq.add(Queries.newNonNestedFilter(), Occur.MUST); return new ConstantScoreQuery(bq); } else if (hasNested) { return Queries.newNonNestedFilter(); } else if (filterPercolateType) { return new ConstantScoreQuery(Queries.not(percolatorType)); } else { return null; } } // if we filter by types, we don't need to filter by non nested docs // since they have different types (starting with __) if (types.length == 1) { DocumentMapper docMapper = documentMapper(types[0]); Query filter = docMapper != null ? docMapper.typeFilter() : new TermQuery(new Term(TypeFieldMapper.NAME, types[0])); if (filterPercolateType) { BooleanQuery bq = new BooleanQuery(); bq.add(percolatorType, Occur.MUST_NOT); bq.add(filter, Occur.MUST); return new ConstantScoreQuery(bq); } else { return filter; } } // see if we can use terms filter boolean useTermsFilter = true; for (String type : types) { DocumentMapper docMapper = documentMapper(type); if (docMapper == null) { useTermsFilter = false; break; } if (docMapper.typeMapper().fieldType().indexOptions() == IndexOptions.NONE) { useTermsFilter = false; break; } } // We only use terms filter is there is a type filter, this means we don't need to check for // hasNested here if (useTermsFilter) { BytesRef[] typesBytes = new BytesRef[types.length]; for (int i = 0; i < typesBytes.length; i++) { typesBytes[i] = new BytesRef(types[i]); } TermsQuery termsFilter = new TermsQuery(TypeFieldMapper.NAME, typesBytes); if (filterPercolateType) { BooleanQuery bq = new BooleanQuery(); bq.add(percolatorType, Occur.MUST_NOT); bq.add(termsFilter, Occur.MUST); return new ConstantScoreQuery(bq); } else { return termsFilter; } } else { // Current bool filter requires that at least one should clause matches, even with a must // clause. BooleanQuery bool = new BooleanQuery(); for (String type : types) { DocumentMapper docMapper = documentMapper(type); if (docMapper == null) { bool.add(new TermQuery(new Term(TypeFieldMapper.NAME, type)), BooleanClause.Occur.SHOULD); } else { bool.add(docMapper.typeFilter(), BooleanClause.Occur.SHOULD); } } if (filterPercolateType) { bool.add(percolatorType, BooleanClause.Occur.MUST_NOT); } if (hasNested) { bool.add(Queries.newNonNestedFilter(), BooleanClause.Occur.MUST); } return new ConstantScoreQuery(bool); } } /** * Returns an {@link MappedFieldType} which has the given index name. * * <p>If multiple types have fields with the same index name, the first is returned. */ public MappedFieldType indexName(String indexName) { return fieldTypes.getByIndexName(indexName); } /** * Returns the {@link MappedFieldType} for the give fullName. * * <p>If multiple types have fields with the same full name, the first is returned. */ public MappedFieldType fullName(String fullName) { return fieldTypes.get(fullName); } /** * Returns all the fields that match the given pattern. If the pattern is prefixed with a type * then the fields will be returned with a type prefix. */ public Collection<String> simpleMatchToIndexNames(String pattern) { if (Regex.isSimpleMatchPattern(pattern) == false) { // no wildcards return Collections.singletonList(pattern); } return fieldTypes.simpleMatchToIndexNames(pattern); } // TODO: remove this since the underlying index names are now the same across all types public Collection<String> simpleMatchToIndexNames(String pattern, @Nullable String[] types) { return simpleMatchToIndexNames(pattern); } // TODO: remove types param, since the object mapper must be the same across all types public ObjectMapper getObjectMapper(String name, @Nullable String[] types) { return fullPathObjectMappers.get(name); } public MappedFieldType smartNameFieldType(String smartName) { MappedFieldType fieldType = fullName(smartName); if (fieldType != null) { return fieldType; } return indexName(smartName); } // TODO: remove this since the underlying index names are now the same across all types public MappedFieldType smartNameFieldType(String smartName, @Nullable String[] types) { return smartNameFieldType(smartName); } /** * Given a type (eg. long, string, ...), return an anonymous field mapper that can be used for * search operations. */ public MappedFieldType unmappedFieldType(String type) { final ImmutableMap<String, MappedFieldType> unmappedFieldMappers = this.unmappedFieldTypes; MappedFieldType fieldType = unmappedFieldMappers.get(type); if (fieldType == null) { final Mapper.TypeParser.ParserContext parserContext = documentMapperParser().parserContext(); Mapper.TypeParser typeParser = parserContext.typeParser(type); if (typeParser == null) { throw new IllegalArgumentException("No mapper found for type [" + type + "]"); } final Mapper.Builder<?, ?> builder = typeParser.parse("__anonymous_" + type, ImmutableMap.<String, Object>of(), parserContext); final BuilderContext builderContext = new BuilderContext(indexSettings, new ContentPath(1)); fieldType = ((FieldMapper) builder.build(builderContext)).fieldType(); // There is no need to synchronize writes here. In the case of concurrent access, we could // just // compute some mappers several times, which is not a big deal this.unmappedFieldTypes = ImmutableMap.<String, MappedFieldType>builder() .putAll(unmappedFieldMappers) .put(type, fieldType) .build(); } return fieldType; } public Analyzer indexAnalyzer() { return this.indexAnalyzer; } public Analyzer searchAnalyzer() { return this.searchAnalyzer; } public Analyzer searchQuoteAnalyzer() { return this.searchQuoteAnalyzer; } /** Resolves the closest inherited {@link ObjectMapper} that is nested. */ public ObjectMapper resolveClosestNestedObjectMapper(String fieldName) { int indexOf = fieldName.lastIndexOf('.'); if (indexOf == -1) { return null; } else { do { String objectPath = fieldName.substring(0, indexOf); ObjectMapper objectMapper = fullPathObjectMappers.get(objectPath); if (objectMapper == null) { indexOf = objectPath.lastIndexOf('.'); continue; } if (objectMapper.nested().isNested()) { return objectMapper; } indexOf = objectPath.lastIndexOf('.'); } while (indexOf != -1); } return null; } public ImmutableSet<String> getParentTypes() { return parentTypes; } /** @return Whether a field is a metadata field. */ public static boolean isMetadataField(String fieldName) { return META_FIELDS.contains(fieldName); } public static String[] getAllMetaFields() { return META_FIELDS.toArray(String.class); } /** An analyzer wrapper that can lookup fields within the index mappings */ final class MapperAnalyzerWrapper extends DelegatingAnalyzerWrapper { private final Analyzer defaultAnalyzer; private final Function<MappedFieldType, Analyzer> extractAnalyzer; MapperAnalyzerWrapper( Analyzer defaultAnalyzer, Function<MappedFieldType, Analyzer> extractAnalyzer) { super(Analyzer.PER_FIELD_REUSE_STRATEGY); this.defaultAnalyzer = defaultAnalyzer; this.extractAnalyzer = extractAnalyzer; } @Override protected Analyzer getWrappedAnalyzer(String fieldName) { MappedFieldType fieldType = smartNameFieldType(fieldName); if (fieldType != null) { Analyzer analyzer = extractAnalyzer.apply(fieldType); if (analyzer != null) { return analyzer; } } return defaultAnalyzer; } } }