Merge pull request #13376 from martijnvg/remove_pre2x_slow_parent_child
Removed pre 2.x parent child implementation
This commit is contained in:
commit
73d84e4797
|
@ -399,7 +399,7 @@ public class MetaDataMappingService extends AbstractComponent {
|
|||
// For example in MapperService we can't distinguish between a create index api call
|
||||
// and a put mapping api call, so we don't which type did exist before.
|
||||
// Also the order of the mappings may be backwards.
|
||||
if (Version.indexCreated(indexService.getIndexSettings()).onOrAfter(Version.V_2_0_0_beta1) && newMapper.parentFieldMapper().active()) {
|
||||
if (newMapper.parentFieldMapper().active()) {
|
||||
IndexMetaData indexMetaData = currentState.metaData().index(index);
|
||||
for (ObjectCursor<MappingMetaData> mapping : indexMetaData.mappings().values()) {
|
||||
if (newMapper.parentFieldMapper().type().equals(mapping.value.type())) {
|
||||
|
|
|
@ -245,16 +245,6 @@ public class IndexFieldDataService extends AbstractIndexComponent {
|
|||
}
|
||||
fieldDataCaches.put(fieldNames.indexName(), cache);
|
||||
}
|
||||
|
||||
// Remove this in 3.0
|
||||
final boolean isOldParentField = ParentFieldMapper.NAME.equals(fieldNames.indexName())
|
||||
&& Version.indexCreated(indexSettings).before(Version.V_2_0_0_beta1);
|
||||
if (isOldParentField) {
|
||||
if (parentIndexFieldData == null) {
|
||||
parentIndexFieldData = builder.build(index, indexSettings, fieldType, cache, circuitBreakerService, mapperService);
|
||||
}
|
||||
return (IFD) parentIndexFieldData;
|
||||
}
|
||||
}
|
||||
|
||||
return (IFD) builder.build(index, indexSettings, fieldType, cache, circuitBreakerService, mapperService);
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* 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.fielddata.plain;
|
||||
|
||||
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
||||
|
||||
import org.apache.lucene.index.DocValues;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.util.Accountable;
|
||||
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
||||
import org.elasticsearch.index.fielddata.AtomicOrdinalsFieldData;
|
||||
import org.elasticsearch.search.MultiValueMode;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ParentChildAtomicFieldData extends AbstractAtomicParentChildFieldData {
|
||||
|
||||
private final ImmutableOpenMap<String, AtomicOrdinalsFieldData> typeToIds;
|
||||
private final long memorySizeInBytes;
|
||||
|
||||
public ParentChildAtomicFieldData(ImmutableOpenMap<String, AtomicOrdinalsFieldData> typeToIds) {
|
||||
this.typeToIds = typeToIds;
|
||||
long size = 0;
|
||||
for (ObjectCursor<AtomicOrdinalsFieldData> cursor : typeToIds.values()) {
|
||||
size += cursor.value.ramBytesUsed();
|
||||
}
|
||||
this.memorySizeInBytes = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long ramBytesUsed() {
|
||||
return memorySizeInBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Accountable> getChildResources() {
|
||||
// TODO: should we break down by type?
|
||||
// the current 'map' does not impl java.util.Map so we cant use Accountables.namedAccountables...
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> types() {
|
||||
final Set<String> types = new HashSet<>();
|
||||
for (ObjectCursor<String> cursor : typeToIds.keys()) {
|
||||
types.add(cursor.value);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedDocValues getOrdinalsValues(String type) {
|
||||
AtomicOrdinalsFieldData atomicFieldData = typeToIds.get(type);
|
||||
if (atomicFieldData != null) {
|
||||
return MultiValueMode.MIN.select(atomicFieldData.getOrdinalsValues());
|
||||
} else {
|
||||
return DocValues.emptySorted();
|
||||
}
|
||||
}
|
||||
|
||||
public AtomicOrdinalsFieldData getAtomicFieldData(String type) {
|
||||
return typeToIds.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (ObjectCursor<AtomicOrdinalsFieldData> cursor : typeToIds.values()) {
|
||||
cursor.value.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* 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.fielddata.plain;
|
||||
|
||||
import org.apache.lucene.index.FilteredTermsEnum;
|
||||
import org.apache.lucene.index.TermsEnum;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
/**
|
||||
* Only emits terms that exist in the parentTypes set.
|
||||
*
|
||||
* @elasticsearch.internal
|
||||
*/
|
||||
final class ParentChildFilteredTermsEnum extends FilteredTermsEnum {
|
||||
|
||||
private final NavigableSet<BytesRef> parentTypes;
|
||||
|
||||
private BytesRef seekTerm;
|
||||
private String type;
|
||||
private BytesRef id;
|
||||
|
||||
ParentChildFilteredTermsEnum(TermsEnum tenum, NavigableSet<BytesRef> parentTypes) {
|
||||
super(tenum, true);
|
||||
this.parentTypes = parentTypes;
|
||||
this.seekTerm = parentTypes.isEmpty() ? null : parentTypes.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BytesRef nextSeekTerm(BytesRef currentTerm) throws IOException {
|
||||
BytesRef temp = seekTerm;
|
||||
seekTerm = null;
|
||||
return temp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AcceptStatus accept(BytesRef term) throws IOException {
|
||||
if (parentTypes.isEmpty()) {
|
||||
return AcceptStatus.END;
|
||||
}
|
||||
|
||||
BytesRef[] typeAndId = Uid.splitUidIntoTypeAndId(term);
|
||||
if (parentTypes.contains(typeAndId[0])) {
|
||||
type = typeAndId[0].utf8ToString();
|
||||
id = typeAndId[1];
|
||||
return AcceptStatus.YES;
|
||||
} else {
|
||||
BytesRef nextType = parentTypes.ceiling(typeAndId[0]);
|
||||
if (nextType == null) {
|
||||
return AcceptStatus.END;
|
||||
}
|
||||
seekTerm = nextType;
|
||||
return AcceptStatus.NO_AND_SEEK;
|
||||
}
|
||||
}
|
||||
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public BytesRef id() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -67,36 +67,24 @@ import java.util.concurrent.TimeUnit;
|
|||
* ParentChildIndexFieldData is responsible for loading the id cache mapping
|
||||
* needed for has_child and has_parent queries into memory.
|
||||
*/
|
||||
public class ParentChildIndexFieldData extends AbstractIndexFieldData<AtomicParentChildFieldData> implements IndexParentChildFieldData, DocumentTypeListener {
|
||||
public class ParentChildIndexFieldData extends AbstractIndexFieldData<AtomicParentChildFieldData> implements IndexParentChildFieldData {
|
||||
|
||||
private final NavigableSet<String> parentTypes;
|
||||
private final Set<String> parentTypes;
|
||||
private final CircuitBreakerService breakerService;
|
||||
|
||||
// If child type (a type with _parent field) is added or removed, we want to make sure modifications don't happen
|
||||
// while loading.
|
||||
private final Object lock = new Object();
|
||||
|
||||
public ParentChildIndexFieldData(Index index, @IndexSettings Settings indexSettings, MappedFieldType.Names fieldNames,
|
||||
FieldDataType fieldDataType, IndexFieldDataCache cache, MapperService mapperService,
|
||||
CircuitBreakerService breakerService) {
|
||||
super(index, indexSettings, fieldNames, fieldDataType, cache);
|
||||
this.breakerService = breakerService;
|
||||
if (Version.indexCreated(indexSettings).before(Version.V_2_0_0_beta1)) {
|
||||
parentTypes = new TreeSet<>();
|
||||
for (DocumentMapper documentMapper : mapperService.docMappers(false)) {
|
||||
beforeCreate(documentMapper);
|
||||
Set<String> parentTypes = new HashSet<>();
|
||||
for (DocumentMapper mapper : mapperService.docMappers(false)) {
|
||||
ParentFieldMapper parentFieldMapper = mapper.parentFieldMapper();
|
||||
if (parentFieldMapper.active()) {
|
||||
parentTypes.add(parentFieldMapper.type());
|
||||
}
|
||||
mapperService.addTypeListener(this);
|
||||
} else {
|
||||
ImmutableSortedSet.Builder<String> builder = ImmutableSortedSet.naturalOrder();
|
||||
for (DocumentMapper mapper : mapperService.docMappers(false)) {
|
||||
ParentFieldMapper parentFieldMapper = mapper.parentFieldMapper();
|
||||
if (parentFieldMapper.active()) {
|
||||
builder.add(parentFieldMapper.type());
|
||||
}
|
||||
}
|
||||
parentTypes = builder.build();
|
||||
}
|
||||
this.parentTypes = parentTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -106,158 +94,47 @@ public class ParentChildIndexFieldData extends AbstractIndexFieldData<AtomicPare
|
|||
|
||||
@Override
|
||||
public AtomicParentChildFieldData load(LeafReaderContext context) {
|
||||
if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)) {
|
||||
final LeafReader reader = context.reader();
|
||||
return new AbstractAtomicParentChildFieldData() {
|
||||
final LeafReader reader = context.reader();
|
||||
return new AbstractAtomicParentChildFieldData() {
|
||||
|
||||
public Set<String> types() {
|
||||
return parentTypes;
|
||||
}
|
||||
public Set<String> types() {
|
||||
return parentTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedDocValues getOrdinalsValues(String type) {
|
||||
try {
|
||||
return DocValues.getSorted(reader, ParentFieldMapper.joinField(type));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("cannot load join doc values field for type [" + type + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long ramBytesUsed() {
|
||||
// unknown
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Accountable> getChildResources() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
try {
|
||||
return cache.load(context, this);
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof ElasticsearchException) {
|
||||
throw (ElasticsearchException) e;
|
||||
} else {
|
||||
throw new ElasticsearchException(e.getMessage(), e);
|
||||
@Override
|
||||
public SortedDocValues getOrdinalsValues(String type) {
|
||||
try {
|
||||
return DocValues.getSorted(reader, ParentFieldMapper.joinField(type));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("cannot load join doc values field for type [" + type + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long ramBytesUsed() {
|
||||
// unknown
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Accountable> getChildResources() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractAtomicParentChildFieldData loadDirect(LeafReaderContext context) throws Exception {
|
||||
// Make this method throw an UnsupportedOperationException in 3.0, only
|
||||
// needed for indices created BEFORE 2.0
|
||||
LeafReader reader = context.reader();
|
||||
final float acceptableTransientOverheadRatio = fieldDataType.getSettings().getAsFloat(
|
||||
"acceptable_transient_overhead_ratio", OrdinalsBuilder.DEFAULT_ACCEPTABLE_OVERHEAD_RATIO
|
||||
);
|
||||
|
||||
final NavigableSet<BytesRef> parentTypes = new TreeSet<>();
|
||||
synchronized (lock) {
|
||||
for (String parentType : this.parentTypes) {
|
||||
parentTypes.add(new BytesRef(parentType));
|
||||
}
|
||||
}
|
||||
boolean success = false;
|
||||
ParentChildAtomicFieldData data = null;
|
||||
ParentChildFilteredTermsEnum termsEnum = new ParentChildFilteredTermsEnum(
|
||||
new ParentChildIntersectTermsEnum(reader, UidFieldMapper.NAME, ParentFieldMapper.NAME),
|
||||
parentTypes
|
||||
);
|
||||
ParentChildEstimator estimator = new ParentChildEstimator(breakerService.getBreaker(CircuitBreaker.FIELDDATA), termsEnum);
|
||||
TermsEnum estimatedTermsEnum = estimator.beforeLoad(null);
|
||||
ObjectObjectHashMap<String, TypeBuilder> typeBuilders = new ObjectObjectHashMap<>();
|
||||
try {
|
||||
try {
|
||||
PostingsEnum docsEnum = null;
|
||||
for (BytesRef term = estimatedTermsEnum.next(); term != null; term = estimatedTermsEnum.next()) {
|
||||
// Usually this would be estimatedTermsEnum, but the
|
||||
// abstract TermsEnum class does not support the .type()
|
||||
// and .id() methods, so we skip using the wrapped
|
||||
// TermsEnum and delegate directly to the
|
||||
// ParentChildFilteredTermsEnum that was originally wrapped
|
||||
String type = termsEnum.type();
|
||||
TypeBuilder typeBuilder = typeBuilders.get(type);
|
||||
if (typeBuilder == null) {
|
||||
typeBuilders.put(type, typeBuilder = new TypeBuilder(acceptableTransientOverheadRatio, reader));
|
||||
}
|
||||
|
||||
BytesRef id = termsEnum.id();
|
||||
final long termOrd = typeBuilder.builder.nextOrdinal();
|
||||
assert termOrd == typeBuilder.termOrdToBytesOffset.size();
|
||||
typeBuilder.termOrdToBytesOffset.add(typeBuilder.bytes.copyUsingLengthPrefix(id));
|
||||
docsEnum = estimatedTermsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
for (int docId = docsEnum.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = docsEnum.nextDoc()) {
|
||||
typeBuilder.builder.addDoc(docId);
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableOpenMap.Builder<String, AtomicOrdinalsFieldData> typeToAtomicFieldData = ImmutableOpenMap.builder(typeBuilders.size());
|
||||
for (ObjectObjectCursor<String, TypeBuilder> cursor : typeBuilders) {
|
||||
PagedBytes.Reader bytesReader = cursor.value.bytes.freeze(true);
|
||||
final Ordinals ordinals = cursor.value.builder.build(fieldDataType.getSettings());
|
||||
|
||||
typeToAtomicFieldData.put(
|
||||
cursor.key,
|
||||
new PagedBytesAtomicFieldData(bytesReader, cursor.value.termOrdToBytesOffset.build(), ordinals)
|
||||
);
|
||||
}
|
||||
data = new ParentChildAtomicFieldData(typeToAtomicFieldData.build());
|
||||
} finally {
|
||||
for (ObjectObjectCursor<String, TypeBuilder> cursor : typeBuilders) {
|
||||
cursor.value.builder.close();
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
return data;
|
||||
} finally {
|
||||
if (success) {
|
||||
estimator.afterLoad(estimatedTermsEnum, data.ramBytesUsed());
|
||||
} else {
|
||||
estimator.afterLoad(estimatedTermsEnum, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCreate(DocumentMapper mapper) {
|
||||
// Remove in 3.0
|
||||
synchronized (lock) {
|
||||
ParentFieldMapper parentFieldMapper = mapper.parentFieldMapper();
|
||||
if (parentFieldMapper.active()) {
|
||||
// A _parent field can never be added to an existing mapping, so a _parent field either exists on
|
||||
// a new created or doesn't exists. This is why we can update the known parent types via DocumentTypeListener
|
||||
if (parentTypes.add(parentFieldMapper.type())) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AtomicParentChildFieldData empty(int maxDoc) {
|
||||
return new ParentChildAtomicFieldData(ImmutableOpenMap.<String, AtomicOrdinalsFieldData>of());
|
||||
}
|
||||
|
||||
class TypeBuilder {
|
||||
|
||||
final PagedBytes bytes;
|
||||
final PackedLongValues.Builder termOrdToBytesOffset;
|
||||
final OrdinalsBuilder builder;
|
||||
|
||||
TypeBuilder(float acceptableTransientOverheadRatio, LeafReader reader) throws IOException {
|
||||
bytes = new PagedBytes(15);
|
||||
termOrdToBytesOffset = PackedLongValues.monotonicBuilder(PackedInts.COMPACT);
|
||||
builder = new OrdinalsBuilder(-1, reader.maxDoc(), acceptableTransientOverheadRatio);
|
||||
}
|
||||
return AbstractAtomicParentChildFieldData.empty();
|
||||
}
|
||||
|
||||
public static class Builder implements IndexFieldData.Builder {
|
||||
|
@ -267,56 +144,7 @@ public class ParentChildIndexFieldData extends AbstractIndexFieldData<AtomicPare
|
|||
IndexFieldDataCache cache, CircuitBreakerService breakerService,
|
||||
MapperService mapperService) {
|
||||
return new ParentChildIndexFieldData(index, indexSettings, fieldType.names(), fieldType.fieldDataType(), cache,
|
||||
mapperService, breakerService);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimator that wraps parent/child id field data by wrapping the data
|
||||
* in a RamAccountingTermsEnum.
|
||||
*/
|
||||
public class ParentChildEstimator implements PerValueEstimator {
|
||||
|
||||
private final CircuitBreaker breaker;
|
||||
private final TermsEnum filteredEnum;
|
||||
|
||||
// The TermsEnum is passed in here instead of being generated in the
|
||||
// beforeLoad() function since it's filtered inside the previous
|
||||
// TermsEnum wrappers
|
||||
public ParentChildEstimator(CircuitBreaker breaker, TermsEnum filteredEnum) {
|
||||
this.breaker = breaker;
|
||||
this.filteredEnum = filteredEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* General overhead for ids is 2 times the length of the ID
|
||||
*/
|
||||
@Override
|
||||
public long bytesPerValue(BytesRef term) {
|
||||
if (term == null) {
|
||||
return 0;
|
||||
}
|
||||
return 2 * term.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the already filtered {@link TermsEnum} in a
|
||||
* {@link RamAccountingTermsEnum} and returns it
|
||||
*/
|
||||
@Override
|
||||
public TermsEnum beforeLoad(Terms terms) throws IOException {
|
||||
return new RamAccountingTermsEnum(filteredEnum, breaker, this, "parent/child id cache");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the breaker based on the difference between the actual usage
|
||||
* and the aggregated estimations.
|
||||
*/
|
||||
@Override
|
||||
public void afterLoad(TermsEnum termsEnum, long actualUsed) {
|
||||
assert termsEnum instanceof RamAccountingTermsEnum;
|
||||
long estimatedBytes = ((RamAccountingTermsEnum) termsEnum).getTotalBytes();
|
||||
breaker.addWithoutBreaking(-(estimatedBytes - actualUsed));
|
||||
mapperService, breakerService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,14 +186,6 @@ public class ParentChildIndexFieldData extends AbstractIndexFieldData<AtomicPare
|
|||
@Override
|
||||
public IndexParentChildFieldData localGlobalDirect(IndexReader indexReader) throws Exception {
|
||||
final long startTime = System.nanoTime();
|
||||
final Set<String> parentTypes;
|
||||
if (Version.indexCreated(indexSettings).before(Version.V_2_0_0_beta1)) {
|
||||
synchronized (lock) {
|
||||
parentTypes = ImmutableSet.copyOf(this.parentTypes);
|
||||
}
|
||||
} else {
|
||||
parentTypes = this.parentTypes;
|
||||
}
|
||||
|
||||
long ramBytesUsed = 0;
|
||||
final Map<String, OrdinalMapAndAtomicFieldData> perType = new HashMap<>();
|
||||
|
|
|
@ -1,335 +0,0 @@
|
|||
/*
|
||||
* 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.fielddata.plain;
|
||||
|
||||
import com.carrotsearch.hppc.IntArrayList;
|
||||
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Intersects the terms and unions the doc ids for terms enum of multiple fields.
|
||||
*
|
||||
* @elasticsearch.internal
|
||||
*/
|
||||
final class ParentChildIntersectTermsEnum extends TermsEnum {
|
||||
|
||||
private final List<TermsEnumState> states;
|
||||
private final IntArrayList stateSlots;
|
||||
|
||||
private BytesRef current;
|
||||
|
||||
ParentChildIntersectTermsEnum(LeafReader atomicReader, String... fields) throws IOException {
|
||||
List<TermsEnum> fieldEnums = new ArrayList<>();
|
||||
for (String field : fields) {
|
||||
Terms terms = atomicReader.terms(field);
|
||||
if (terms != null) {
|
||||
fieldEnums.add(terms.iterator());
|
||||
}
|
||||
}
|
||||
states = new ArrayList<>(fieldEnums.size());
|
||||
for (TermsEnum tEnum : fieldEnums) {
|
||||
states.add(new TermsEnumState(tEnum));
|
||||
}
|
||||
stateSlots = new IntArrayList(states.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BytesRef term() throws IOException {
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PostingsEnum postings(PostingsEnum reuse, int flags) throws IOException {
|
||||
int size = stateSlots.size();
|
||||
assert size > 0;
|
||||
if (size == 1) {
|
||||
// Can't use 'reuse' since we don't know to which previous TermsEnum it belonged to.
|
||||
return states.get(stateSlots.get(0)).termsEnum.postings(null, flags);
|
||||
} else {
|
||||
List<PostingsEnum> docsEnums = new ArrayList<>(stateSlots.size());
|
||||
for (int i = 0; i < stateSlots.size(); i++) {
|
||||
docsEnums.add(states.get(stateSlots.get(i)).termsEnum.postings(null, flags));
|
||||
}
|
||||
return new CompoundDocsEnum(docsEnums);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BytesRef next() throws IOException {
|
||||
if (states.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
// unpositioned
|
||||
for (TermsEnumState state : states) {
|
||||
state.initialize();
|
||||
}
|
||||
} else {
|
||||
int removed = 0;
|
||||
for (int i = 0; i < stateSlots.size(); i++) {
|
||||
int stateSlot = stateSlots.get(i);
|
||||
if (states.get(stateSlot - removed).next() == null) {
|
||||
states.remove(stateSlot - removed);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (states.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
stateSlots.clear();
|
||||
}
|
||||
|
||||
BytesRef lowestTerm = states.get(0).term;
|
||||
stateSlots.add(0);
|
||||
for (int i = 1; i < states.size(); i++) {
|
||||
TermsEnumState state = states.get(i);
|
||||
int cmp = lowestTerm.compareTo(state.term);
|
||||
if (cmp > 0) {
|
||||
lowestTerm = state.term;
|
||||
stateSlots.clear();
|
||||
stateSlots.add(i);
|
||||
} else if (cmp == 0) {
|
||||
stateSlots.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return current = lowestTerm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekStatus seekCeil(BytesRef text) throws IOException {
|
||||
if (states.isEmpty()) {
|
||||
return SeekStatus.END;
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
if (current == null) {
|
||||
// unpositioned
|
||||
Iterator<TermsEnumState> iterator = states.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
SeekStatus seekStatus = iterator.next().seekCeil(text);
|
||||
if (seekStatus == SeekStatus.END) {
|
||||
iterator.remove();
|
||||
} else if (seekStatus == SeekStatus.FOUND) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int removed = 0;
|
||||
for (int i = 0; i < stateSlots.size(); i++) {
|
||||
int stateSlot = stateSlots.get(i);
|
||||
SeekStatus seekStatus = states.get(stateSlot - removed).seekCeil(text);
|
||||
if (seekStatus == SeekStatus.END) {
|
||||
states.remove(stateSlot - removed);
|
||||
removed++;
|
||||
} else if (seekStatus == SeekStatus.FOUND) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (states.isEmpty()) {
|
||||
return SeekStatus.END;
|
||||
}
|
||||
stateSlots.clear();
|
||||
|
||||
if (found) {
|
||||
for (int i = 0; i < states.size(); i++) {
|
||||
if (states.get(i).term.equals(text)) {
|
||||
stateSlots.add(i);
|
||||
}
|
||||
}
|
||||
current = text;
|
||||
return SeekStatus.FOUND;
|
||||
} else {
|
||||
BytesRef lowestTerm = states.get(0).term;
|
||||
stateSlots.add(0);
|
||||
for (int i = 1; i < states.size(); i++) {
|
||||
TermsEnumState state = states.get(i);
|
||||
int cmp = lowestTerm.compareTo(state.term);
|
||||
if (cmp > 0) {
|
||||
lowestTerm = state.term;
|
||||
stateSlots.clear();
|
||||
stateSlots.add(i);
|
||||
} else if (cmp == 0) {
|
||||
stateSlots.add(i);
|
||||
}
|
||||
}
|
||||
current = lowestTerm;
|
||||
return SeekStatus.NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
class TermsEnumState {
|
||||
|
||||
final TermsEnum termsEnum;
|
||||
BytesRef term;
|
||||
SeekStatus lastSeekStatus;
|
||||
|
||||
TermsEnumState(TermsEnum termsEnum) {
|
||||
this.termsEnum = termsEnum;
|
||||
}
|
||||
|
||||
void initialize() throws IOException {
|
||||
term = termsEnum.next();
|
||||
}
|
||||
|
||||
BytesRef next() throws IOException {
|
||||
return term = termsEnum.next();
|
||||
}
|
||||
|
||||
SeekStatus seekCeil(BytesRef text) throws IOException {
|
||||
lastSeekStatus = termsEnum.seekCeil(text);
|
||||
if (lastSeekStatus != SeekStatus.END) {
|
||||
term = termsEnum.term();
|
||||
}
|
||||
return lastSeekStatus;
|
||||
}
|
||||
}
|
||||
|
||||
class CompoundDocsEnum extends PostingsEnum {
|
||||
|
||||
final List<State> states;
|
||||
int current = -1;
|
||||
|
||||
CompoundDocsEnum(List<PostingsEnum> docsEnums) {
|
||||
this.states = new ArrayList<>(docsEnums.size());
|
||||
for (PostingsEnum docsEnum : docsEnums) {
|
||||
states.add(new State(docsEnum));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
if (states.isEmpty()) {
|
||||
return current = NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
if (current == -1) {
|
||||
for (State state : states) {
|
||||
state.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
int lowestIndex = 0;
|
||||
int lowestDocId = states.get(0).current;
|
||||
for (int i = 1; i < states.size(); i++) {
|
||||
State state = states.get(i);
|
||||
if (lowestDocId > state.current) {
|
||||
lowestDocId = state.current;
|
||||
lowestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (states.get(lowestIndex).next() == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
states.remove(lowestIndex);
|
||||
}
|
||||
|
||||
return current = lowestDocId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int endOffset() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BytesRef getPayload() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextPosition() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startOffset() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
class State {
|
||||
|
||||
final PostingsEnum docsEnum;
|
||||
int current = -1;
|
||||
|
||||
State(PostingsEnum docsEnum) {
|
||||
this.docsEnum = docsEnum;
|
||||
}
|
||||
|
||||
void initialize() throws IOException {
|
||||
current = docsEnum.nextDoc();
|
||||
}
|
||||
|
||||
int next() throws IOException {
|
||||
return current = docsEnum.nextDoc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long ord() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekExact(long ord) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docFreq() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalTermFreq() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -263,7 +263,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
|
|||
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())) {
|
||||
if (mapper.type().equals(mapper.parentFieldMapper().type())) {
|
||||
throw new IllegalArgumentException("The [_parent.type] option can't point to the same type");
|
||||
}
|
||||
if (typeNameStartsWithIllegalDot(mapper)) {
|
||||
|
|
|
@ -37,8 +37,6 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
|
|||
|
||||
private Integer maxChildren;
|
||||
|
||||
private Integer shortCircuitCutoff;
|
||||
|
||||
private String queryName;
|
||||
|
||||
private QueryInnerHitBuilder innerHit = null;
|
||||
|
@ -82,15 +80,6 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures at what cut off point only to evaluate parent documents that contain the matching parent id terms
|
||||
* instead of evaluating all parent docs.
|
||||
*/
|
||||
public HasChildQueryBuilder setShortCircuitCutoff(int shortCircuitCutoff) {
|
||||
this.shortCircuitCutoff = shortCircuitCutoff;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
|
||||
*/
|
||||
|
@ -125,9 +114,6 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
|
|||
if (maxChildren != null) {
|
||||
builder.field("max_children", maxChildren);
|
||||
}
|
||||
if (shortCircuitCutoff != null) {
|
||||
builder.field("short_circuit_cutoff", shortCircuitCutoff);
|
||||
}
|
||||
if (queryName != null) {
|
||||
builder.field("_name", queryName);
|
||||
}
|
||||
|
|
|
@ -21,14 +21,10 @@ package org.elasticsearch.index.query;
|
|||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.MultiDocValues;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.QueryWrapperFilter;
|
||||
import org.apache.lucene.search.join.BitSetProducer;
|
||||
import org.apache.lucene.search.join.JoinUtil;
|
||||
import org.apache.lucene.search.join.ScoreMode;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
|
@ -40,9 +36,6 @@ import org.elasticsearch.index.mapper.DocumentMapper;
|
|||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
|
||||
import org.elasticsearch.index.query.support.XContentStructure;
|
||||
import org.elasticsearch.index.search.child.ChildrenConstantScoreQuery;
|
||||
import org.elasticsearch.index.search.child.ChildrenQuery;
|
||||
import org.elasticsearch.index.search.child.ScoreType;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
@ -79,7 +72,6 @@ public class HasChildQueryParser implements QueryParser {
|
|||
ScoreType scoreType = ScoreType.NONE;
|
||||
int minChildren = 0;
|
||||
int maxChildren = 0;
|
||||
int shortCircuitParentDocSet = 8192;
|
||||
String queryName = null;
|
||||
InnerHitsSubSearchContext innerHits = null;
|
||||
|
||||
|
@ -117,8 +109,6 @@ public class HasChildQueryParser implements QueryParser {
|
|||
minChildren = parser.intValue(true);
|
||||
} else if ("max_children".equals(currentFieldName) || "maxChildren".equals(currentFieldName)) {
|
||||
maxChildren = parser.intValue(true);
|
||||
} else if ("short_circuit_cutoff".equals(currentFieldName)) {
|
||||
shortCircuitParentDocSet = parser.intValue();
|
||||
} else if ("_name".equals(currentFieldName)) {
|
||||
queryName = parser.text();
|
||||
} else {
|
||||
|
@ -167,29 +157,12 @@ public class HasChildQueryParser implements QueryParser {
|
|||
throw new QueryParsingException(parseContext, "[has_child] 'max_children' is less than 'min_children'");
|
||||
}
|
||||
|
||||
BitSetProducer nonNestedDocsFilter = null;
|
||||
if (parentDocMapper.hasNestedObjects()) {
|
||||
nonNestedDocsFilter = parseContext.bitsetFilter(Queries.newNonNestedFilter());
|
||||
}
|
||||
|
||||
// wrap the query with type query
|
||||
innerQuery = Queries.filtered(innerQuery, childDocMapper.typeFilter());
|
||||
|
||||
final Query query;
|
||||
final ParentChildIndexFieldData parentChildIndexFieldData = parseContext.getForField(parentFieldMapper.fieldType());
|
||||
if (parseContext.indexVersionCreated().onOrAfter(Version.V_2_0_0_beta1)) {
|
||||
query = joinUtilHelper(parentType, parentChildIndexFieldData, parentDocMapper.typeFilter(), scoreType, innerQuery, minChildren, maxChildren);
|
||||
} else {
|
||||
// TODO: use the query API
|
||||
Filter parentFilter = new QueryWrapperFilter(parentDocMapper.typeFilter());
|
||||
if (minChildren > 1 || maxChildren > 0 || scoreType != ScoreType.NONE) {
|
||||
query = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter, innerQuery, scoreType, minChildren,
|
||||
maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
} else {
|
||||
query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentType, childType, parentFilter,
|
||||
shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
}
|
||||
}
|
||||
query = joinUtilHelper(parentType, parentChildIndexFieldData, parentDocMapper.typeFilter(), scoreType, innerQuery, minChildren, maxChildren);
|
||||
if (queryName != null) {
|
||||
parseContext.addNamedQuery(queryName, query);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.apache.lucene.search.*;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
|
@ -30,9 +29,6 @@ import org.elasticsearch.index.mapper.DocumentMapper;
|
|||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
|
||||
import org.elasticsearch.index.query.support.XContentStructure;
|
||||
import org.elasticsearch.index.search.child.ParentConstantScoreQuery;
|
||||
import org.elasticsearch.index.search.child.ParentQuery;
|
||||
import org.elasticsearch.index.search.child.ScoreType;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
|
||||
|
@ -173,11 +169,11 @@ public class HasParentQueryParser implements QueryParser {
|
|||
throw new QueryParsingException(parseContext, "[has_parent] no _parent field configured");
|
||||
}
|
||||
|
||||
Query parentFilter = null;
|
||||
Query parentTypeQuery = null;
|
||||
if (parentTypes.size() == 1) {
|
||||
DocumentMapper documentMapper = parseContext.mapperService().documentMapper(parentTypes.iterator().next());
|
||||
if (documentMapper != null) {
|
||||
parentFilter = documentMapper.typeFilter();
|
||||
parentTypeQuery = documentMapper.typeFilter();
|
||||
}
|
||||
} else {
|
||||
BooleanQuery.Builder parentsFilter = new BooleanQuery.Builder();
|
||||
|
@ -187,26 +183,18 @@ public class HasParentQueryParser implements QueryParser {
|
|||
parentsFilter.add(documentMapper.typeFilter(), BooleanClause.Occur.SHOULD);
|
||||
}
|
||||
}
|
||||
parentFilter = parentsFilter.build();
|
||||
parentTypeQuery = parentsFilter.build();
|
||||
}
|
||||
|
||||
if (parentFilter == null) {
|
||||
if (parentTypeQuery == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// wrap the query with type query
|
||||
innerQuery = Queries.filtered(innerQuery, parentDocMapper.typeFilter());
|
||||
Filter childrenFilter = new QueryWrapperFilter(Queries.not(parentFilter));
|
||||
if (parseContext.indexVersionCreated().onOrAfter(Version.V_2_0_0_beta1)) {
|
||||
ScoreType scoreMode = score ? ScoreType.MAX : ScoreType.NONE;
|
||||
return joinUtilHelper(parentType, parentChildIndexFieldData, childrenFilter, scoreMode, innerQuery, 0, Integer.MAX_VALUE);
|
||||
} else {
|
||||
if (score) {
|
||||
return new ParentQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter);
|
||||
} else {
|
||||
return new ParentConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentDocMapper.type(), childrenFilter);
|
||||
}
|
||||
}
|
||||
Query childrenFilter = Queries.not(parentTypeQuery);
|
||||
ScoreType scoreMode = score ? ScoreType.MAX : ScoreType.NONE;
|
||||
return joinUtilHelper(parentType, parentChildIndexFieldData, childrenFilter, scoreMode, innerQuery, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.index.search.child;
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
|
||||
/**
|
|
@ -1,304 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BitsFilteredDocIdSet;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.CollectionTerminatedException;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.search.XFilteredDocIdSetIterator;
|
||||
import org.apache.lucene.search.join.BitSetProducer;
|
||||
import org.apache.lucene.util.LongBitSet;
|
||||
import org.elasticsearch.common.lucene.IndexCacheableQuery;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.lucene.search.NoopCollector;
|
||||
import org.elasticsearch.index.fielddata.AtomicParentChildFieldData;
|
||||
import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
// TODO: Remove me and move the logic to ChildrenQuery when needsScore=false
|
||||
public class ChildrenConstantScoreQuery extends IndexCacheableQuery {
|
||||
|
||||
private final IndexParentChildFieldData parentChildIndexFieldData;
|
||||
private final Query childQuery;
|
||||
private final String parentType;
|
||||
private final String childType;
|
||||
private final Filter parentFilter;
|
||||
private final int shortCircuitParentDocSet;
|
||||
private final BitSetProducer nonNestedDocsFilter;
|
||||
|
||||
public ChildrenConstantScoreQuery(IndexParentChildFieldData parentChildIndexFieldData, Query childQuery, String parentType, String childType, Filter parentFilter, int shortCircuitParentDocSet, BitSetProducer nonNestedDocsFilter) {
|
||||
this.parentChildIndexFieldData = parentChildIndexFieldData;
|
||||
this.parentFilter = parentFilter;
|
||||
this.parentType = parentType;
|
||||
this.childType = childType;
|
||||
this.childQuery = childQuery;
|
||||
this.shortCircuitParentDocSet = shortCircuitParentDocSet;
|
||||
this.nonNestedDocsFilter = nonNestedDocsFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) throws IOException {
|
||||
final Query childRewritten = childQuery.rewrite(reader);
|
||||
if (childRewritten != childQuery) {
|
||||
ChildrenConstantScoreQuery rewritten = new ChildrenConstantScoreQuery(parentChildIndexFieldData, childRewritten, parentType, childType, parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
rewritten.setBoost(getBoost());
|
||||
return rewritten;
|
||||
}
|
||||
return super.rewrite(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight doCreateWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
SearchContext sc = SearchContext.current();
|
||||
IndexParentChildFieldData globalIfd = parentChildIndexFieldData.loadGlobal(searcher.getIndexReader());
|
||||
|
||||
final long valueCount;
|
||||
List<LeafReaderContext> leaves = searcher.getIndexReader().leaves();
|
||||
if (globalIfd == null || leaves.isEmpty()) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
} else {
|
||||
AtomicParentChildFieldData afd = globalIfd.load(leaves.get(0));
|
||||
SortedDocValues globalValues = afd.getOrdinalsValues(parentType);
|
||||
valueCount = globalValues.getValueCount();
|
||||
}
|
||||
|
||||
if (valueCount == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
ParentOrdCollector collector = new ParentOrdCollector(globalIfd, valueCount, parentType);
|
||||
searcher.search(childQuery, collector);
|
||||
|
||||
final long remaining = collector.foundParents();
|
||||
if (remaining == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
Filter shortCircuitFilter = null;
|
||||
if (remaining <= shortCircuitParentDocSet) {
|
||||
shortCircuitFilter = ParentIdsFilter.createShortCircuitFilter(
|
||||
nonNestedDocsFilter, sc, parentType, collector.values, collector.parentOrds, remaining
|
||||
);
|
||||
}
|
||||
return new ParentWeight(this, parentFilter, globalIfd, shortCircuitFilter, collector, remaining);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (super.equals(obj) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChildrenConstantScoreQuery that = (ChildrenConstantScoreQuery) obj;
|
||||
if (!childQuery.equals(that.childQuery)) {
|
||||
return false;
|
||||
}
|
||||
if (!childType.equals(that.childType)) {
|
||||
return false;
|
||||
}
|
||||
if (shortCircuitParentDocSet != that.shortCircuitParentDocSet) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + childQuery.hashCode();
|
||||
result = 31 * result + childType.hashCode();
|
||||
result = 31 * result + shortCircuitParentDocSet;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return "child_filter[" + childType + "/" + parentType + "](" + childQuery + ')';
|
||||
}
|
||||
|
||||
private final class ParentWeight extends Weight {
|
||||
|
||||
private final Filter parentFilter;
|
||||
private final Filter shortCircuitFilter;
|
||||
private final ParentOrdCollector collector;
|
||||
private final IndexParentChildFieldData globalIfd;
|
||||
|
||||
private long remaining;
|
||||
private float queryNorm;
|
||||
private float queryWeight;
|
||||
|
||||
public ParentWeight(Query query, Filter parentFilter, IndexParentChildFieldData globalIfd, Filter shortCircuitFilter, ParentOrdCollector collector, long remaining) {
|
||||
super(query);
|
||||
this.parentFilter = parentFilter;
|
||||
this.globalIfd = globalIfd;
|
||||
this.shortCircuitFilter = shortCircuitFilter;
|
||||
this.collector = collector;
|
||||
this.remaining = remaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractTerms(Set<Term> terms) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
|
||||
return Explanation.match(getBoost(), "not implemented yet...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getValueForNormalization() throws IOException {
|
||||
queryWeight = getBoost();
|
||||
return queryWeight * queryWeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
this.queryNorm = norm * topLevelBoost;
|
||||
queryWeight *= this.queryNorm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
if (remaining == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shortCircuitFilter != null) {
|
||||
DocIdSet docIdSet = shortCircuitFilter.getDocIdSet(context, null);
|
||||
if (!Lucene.isEmpty(docIdSet)) {
|
||||
DocIdSetIterator iterator = docIdSet.iterator();
|
||||
if (iterator != null) {
|
||||
return ConstantScorer.create(iterator, this, queryWeight);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
DocIdSet parentDocIdSet = this.parentFilter.getDocIdSet(context, null);
|
||||
if (!Lucene.isEmpty(parentDocIdSet)) {
|
||||
// We can't be sure of the fact that liveDocs have been applied, so we apply it here. The "remaining"
|
||||
// count down (short circuit) logic will then work as expected.
|
||||
parentDocIdSet = BitsFilteredDocIdSet.wrap(parentDocIdSet, context.reader().getLiveDocs());
|
||||
DocIdSetIterator innerIterator = parentDocIdSet.iterator();
|
||||
if (innerIterator != null) {
|
||||
LongBitSet parentOrds = collector.parentOrds;
|
||||
SortedDocValues globalValues = globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
if (globalValues != null) {
|
||||
DocIdSetIterator parentIdIterator = new ParentOrdIterator(innerIterator, parentOrds, globalValues, this);
|
||||
return ConstantScorer.create(parentIdIterator, this, queryWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static class ParentOrdCollector extends NoopCollector {
|
||||
|
||||
private final LongBitSet parentOrds;
|
||||
private final IndexParentChildFieldData indexFieldData;
|
||||
private final String parentType;
|
||||
|
||||
private SortedDocValues values;
|
||||
|
||||
private ParentOrdCollector(IndexParentChildFieldData indexFieldData, long maxOrd, String parentType) {
|
||||
// TODO: look into reusing LongBitSet#bits array
|
||||
this.parentOrds = new LongBitSet(maxOrd + 1);
|
||||
this.indexFieldData = indexFieldData;
|
||||
this.parentType = parentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
if (values != null) {
|
||||
int globalOrdinal = values.getOrd(doc);
|
||||
// TODO: oversize the long bitset and remove the branch
|
||||
if (globalOrdinal >= 0) {
|
||||
parentOrds.set(globalOrdinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSetNextReader(LeafReaderContext context) throws IOException {
|
||||
values = indexFieldData.load(context).getOrdinalsValues(parentType);
|
||||
}
|
||||
|
||||
long foundParents() {
|
||||
return parentOrds.cardinality();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static class ParentOrdIterator extends XFilteredDocIdSetIterator {
|
||||
|
||||
private final LongBitSet parentOrds;
|
||||
private final SortedDocValues ordinals;
|
||||
private final ParentWeight parentWeight;
|
||||
|
||||
private ParentOrdIterator(DocIdSetIterator innerIterator, LongBitSet parentOrds, SortedDocValues ordinals, ParentWeight parentWeight) {
|
||||
super(innerIterator);
|
||||
this.parentOrds = parentOrds;
|
||||
this.ordinals = ordinals;
|
||||
this.parentWeight = parentWeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(int doc) {
|
||||
if (parentWeight.remaining == 0) {
|
||||
throw new CollectionTerminatedException();
|
||||
}
|
||||
|
||||
long parentOrd = ordinals.getOrd(doc);
|
||||
if (parentOrd >= 0) {
|
||||
boolean match = parentOrds.get(parentOrd);
|
||||
if (match) {
|
||||
parentWeight.remaining--;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,724 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BitsFilteredDocIdSet;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.CollectionTerminatedException;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.search.XFilteredDocIdSetIterator;
|
||||
import org.apache.lucene.search.join.BitSetProducer;
|
||||
import org.apache.lucene.util.ToStringUtils;
|
||||
import org.elasticsearch.common.lease.Releasable;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.lucene.IndexCacheableQuery;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.lucene.search.NoopCollector;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.common.util.FloatArray;
|
||||
import org.elasticsearch.common.util.IntArray;
|
||||
import org.elasticsearch.common.util.LongHash;
|
||||
import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.internal.SearchContext.Lifetime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A query implementation that executes the wrapped child query and connects all the matching child docs to the related
|
||||
* parent documents using {@link ParentChildIndexFieldData}.
|
||||
* <p/>
|
||||
* This query is executed in two rounds. The first round resolves all the matching child documents and groups these
|
||||
* documents by parent uid value. Also the child scores are aggregated per parent uid value. During the second round
|
||||
* all parent documents having the same uid value that is collected in the first phase are emitted as hit including
|
||||
* a score based on the aggregated child scores and score type.
|
||||
*/
|
||||
public final class ChildrenQuery extends IndexCacheableQuery {
|
||||
|
||||
protected final ParentChildIndexFieldData ifd;
|
||||
protected final String parentType;
|
||||
protected final String childType;
|
||||
protected final Filter parentFilter;
|
||||
protected final ScoreType scoreType;
|
||||
protected Query childQuery;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
protected final int shortCircuitParentDocSet;
|
||||
protected final BitSetProducer nonNestedDocsFilter;
|
||||
|
||||
public ChildrenQuery(ParentChildIndexFieldData ifd, String parentType, String childType, Filter parentFilter, Query childQuery, ScoreType scoreType, int minChildren, int maxChildren, int shortCircuitParentDocSet, BitSetProducer nonNestedDocsFilter) {
|
||||
this.ifd = ifd;
|
||||
this.parentType = parentType;
|
||||
this.childType = childType;
|
||||
this.parentFilter = parentFilter;
|
||||
this.childQuery = childQuery;
|
||||
this.scoreType = scoreType;
|
||||
this.shortCircuitParentDocSet = shortCircuitParentDocSet;
|
||||
this.nonNestedDocsFilter = nonNestedDocsFilter;
|
||||
assert maxChildren == 0 || minChildren <= maxChildren;
|
||||
this.minChildren = minChildren > 1 ? minChildren : 0;
|
||||
this.maxChildren = maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) throws IOException {
|
||||
final Query childRewritten = childQuery.rewrite(reader);
|
||||
if (childRewritten != childQuery) {
|
||||
Query rewritten = new ChildrenQuery(ifd, parentType, childType, parentFilter, childRewritten, scoreType, minChildren, maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
rewritten.setBoost(getBoost());
|
||||
return rewritten;
|
||||
}
|
||||
return super.rewrite(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (super.equals(obj) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChildrenQuery that = (ChildrenQuery) obj;
|
||||
if (!childQuery.equals(that.childQuery)) {
|
||||
return false;
|
||||
}
|
||||
if (!childType.equals(that.childType)) {
|
||||
return false;
|
||||
}
|
||||
if (minChildren != that.minChildren) {
|
||||
return false;
|
||||
}
|
||||
if (maxChildren != that.maxChildren) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + childQuery.hashCode();
|
||||
result = 31 * result + childType.hashCode();
|
||||
result = 31 * result + minChildren;
|
||||
result = 31 * result + maxChildren;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
int max = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
return "ChildrenQuery[min(" + Integer.toString(minChildren) + ") max(" + Integer.toString(max) + ")of " + childType + "/"
|
||||
+ parentType + "](" + childQuery.toString(field) + ')' + ToStringUtils.boost(getBoost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight doCreateWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
SearchContext sc = SearchContext.current();
|
||||
|
||||
IndexParentChildFieldData globalIfd = ifd.loadGlobal(searcher.getIndexReader());
|
||||
if (globalIfd == null) {
|
||||
// No docs of the specified type exist on this shard
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
boolean abort = true;
|
||||
long numFoundParents;
|
||||
ParentCollector collector = null;
|
||||
try {
|
||||
if (minChildren == 0 && maxChildren == 0 && scoreType != ScoreType.NONE) {
|
||||
switch (scoreType) {
|
||||
case MIN:
|
||||
collector = new MinCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
case MAX:
|
||||
collector = new MaxCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
case SUM:
|
||||
collector = new SumCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (collector == null) {
|
||||
switch (scoreType) {
|
||||
case MIN:
|
||||
collector = new MinCountCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
case MAX:
|
||||
collector = new MaxCountCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
case SUM:
|
||||
case AVG:
|
||||
collector = new SumCountAndAvgCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
case NONE:
|
||||
collector = new CountCollector(globalIfd, sc, parentType);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Are we missing a score type here? -- " + scoreType);
|
||||
}
|
||||
}
|
||||
|
||||
searcher.search(childQuery, collector);
|
||||
numFoundParents = collector.foundParents();
|
||||
if (numFoundParents == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
abort = false;
|
||||
} finally {
|
||||
if (abort) {
|
||||
Releasables.close(collector);
|
||||
}
|
||||
}
|
||||
sc.addReleasable(collector, Lifetime.COLLECTION);
|
||||
final Filter parentFilter;
|
||||
if (numFoundParents <= shortCircuitParentDocSet) {
|
||||
parentFilter = ParentIdsFilter.createShortCircuitFilter(nonNestedDocsFilter, sc, parentType, collector.values,
|
||||
collector.parentIdxs, numFoundParents);
|
||||
} else {
|
||||
parentFilter = this.parentFilter;
|
||||
}
|
||||
return new ParentWeight(this, childQuery.createWeight(searcher, needsScores), parentFilter, numFoundParents, collector, minChildren,
|
||||
maxChildren);
|
||||
}
|
||||
|
||||
protected class ParentWeight extends Weight {
|
||||
|
||||
protected final Weight childWeight;
|
||||
protected final Filter parentFilter;
|
||||
protected final ParentCollector collector;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
|
||||
protected long remaining;
|
||||
protected float queryNorm;
|
||||
protected float queryWeight;
|
||||
|
||||
protected ParentWeight(Query query, Weight childWeight, Filter parentFilter, long remaining, ParentCollector collector, int minChildren, int maxChildren) {
|
||||
super(query);
|
||||
this.childWeight = childWeight;
|
||||
this.parentFilter = parentFilter;
|
||||
this.remaining = remaining;
|
||||
this.collector = collector;
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractTerms(Set<Term> terms) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
|
||||
return Explanation.match(getBoost(), "not implemented yet...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
this.queryNorm = norm * topLevelBoost;
|
||||
queryWeight *= this.queryNorm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getValueForNormalization() throws IOException {
|
||||
queryWeight = getBoost();
|
||||
if (scoreType == ScoreType.NONE) {
|
||||
return queryWeight * queryWeight;
|
||||
}
|
||||
float sum = childWeight.getValueForNormalization();
|
||||
sum *= queryWeight * queryWeight;
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
DocIdSet parentsSet = parentFilter.getDocIdSet(context, null);
|
||||
if (Lucene.isEmpty(parentsSet) || remaining == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can't be sure of the fact that liveDocs have been applied, so we apply it here. The "remaining"
|
||||
// count down (short circuit) logic will then work as expected.
|
||||
DocIdSetIterator parents = BitsFilteredDocIdSet.wrap(parentsSet, context.reader().getLiveDocs()).iterator();
|
||||
|
||||
if (parents != null) {
|
||||
SortedDocValues bytesValues = collector.globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
if (bytesValues == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (minChildren > 0 || maxChildren != 0 || scoreType == ScoreType.NONE) {
|
||||
switch (scoreType) {
|
||||
case NONE:
|
||||
DocIdSetIterator parentIdIterator = new CountParentOrdIterator(this, parents, collector, bytesValues,
|
||||
minChildren, maxChildren);
|
||||
return ConstantScorer.create(parentIdIterator, this, queryWeight);
|
||||
case AVG:
|
||||
return new AvgParentCountScorer(this, parents, collector, bytesValues, minChildren, maxChildren);
|
||||
default:
|
||||
return new ParentCountScorer(this, parents, collector, bytesValues, minChildren, maxChildren);
|
||||
}
|
||||
}
|
||||
switch (scoreType) {
|
||||
case AVG:
|
||||
return new AvgParentScorer(this, parents, collector, bytesValues);
|
||||
default:
|
||||
return new ParentScorer(this, parents, collector, bytesValues);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract static class ParentCollector extends NoopCollector implements Releasable {
|
||||
|
||||
protected final IndexParentChildFieldData globalIfd;
|
||||
protected final LongHash parentIdxs;
|
||||
protected final BigArrays bigArrays;
|
||||
protected final SearchContext searchContext;
|
||||
protected final String parentType;
|
||||
|
||||
protected SortedDocValues values;
|
||||
protected Scorer scorer;
|
||||
|
||||
protected ParentCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
this.globalIfd = globalIfd;
|
||||
this.searchContext = searchContext;
|
||||
this.bigArrays = searchContext.bigArrays();
|
||||
this.parentIdxs = new LongHash(512, bigArrays);
|
||||
this.parentType = parentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void collect(int doc) throws IOException {
|
||||
if (values != null) {
|
||||
final long globalOrdinal = values.getOrd(doc);
|
||||
if (globalOrdinal >= 0) {
|
||||
long parentIdx = parentIdxs.add(globalOrdinal);
|
||||
if (parentIdx >= 0) {
|
||||
newParent(parentIdx);
|
||||
} else {
|
||||
parentIdx = -1 - parentIdx;
|
||||
existingParent(parentIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
}
|
||||
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
}
|
||||
|
||||
public long foundParents() {
|
||||
return parentIdxs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSetNextReader(LeafReaderContext context) throws IOException {
|
||||
values = globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) throws IOException {
|
||||
this.scorer = scorer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Releasables.close(parentIdxs);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract static class ParentScoreCollector extends ParentCollector implements Releasable {
|
||||
|
||||
protected FloatArray scores;
|
||||
|
||||
protected ParentScoreCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
this.scores = this.bigArrays.newFloatArray(512, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsScores() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Releasables.close(parentIdxs, scores);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract static class ParentScoreCountCollector extends ParentScoreCollector implements Releasable {
|
||||
|
||||
protected IntArray occurrences;
|
||||
|
||||
protected ParentScoreCountCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
this.occurrences = bigArrays.newIntArray(512, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
occurrences = bigArrays.grow(occurrences, parentIdx + 1);
|
||||
occurrences.set(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Releasables.close(parentIdxs, scores, occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CountCollector extends ParentCollector implements Releasable {
|
||||
|
||||
protected IntArray occurrences;
|
||||
|
||||
protected CountCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
this.occurrences = bigArrays.newIntArray(512, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
occurrences = bigArrays.grow(occurrences, parentIdx + 1);
|
||||
occurrences.set(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Releasables.close(parentIdxs, occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class SumCollector extends ParentScoreCollector {
|
||||
|
||||
private SumCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
scores.increment(parentIdx, scorer.score());
|
||||
}
|
||||
}
|
||||
|
||||
private final static class MaxCollector extends ParentScoreCollector {
|
||||
|
||||
private MaxCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore > scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class MinCollector extends ParentScoreCollector {
|
||||
|
||||
private MinCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore < scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class MaxCountCollector extends ParentScoreCountCollector {
|
||||
|
||||
private MaxCountCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore > scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class MinCountCollector extends ParentScoreCountCollector {
|
||||
|
||||
private MinCountCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore < scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class SumCountAndAvgCollector extends ParentScoreCountCollector {
|
||||
|
||||
SumCountAndAvgCollector(IndexParentChildFieldData globalIfd, SearchContext searchContext, String parentType) {
|
||||
super(globalIfd, searchContext, parentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
scores.increment(parentIdx, scorer.score());
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ParentScorer extends Scorer {
|
||||
|
||||
final ParentWeight parentWeight;
|
||||
final LongHash parentIds;
|
||||
final FloatArray scores;
|
||||
|
||||
final SortedDocValues globalOrdinals;
|
||||
final DocIdSetIterator parentsIterator;
|
||||
|
||||
int currentDocId = -1;
|
||||
float currentScore;
|
||||
|
||||
ParentScorer(ParentWeight parentWeight, DocIdSetIterator parentsIterator, ParentCollector collector, SortedDocValues globalOrdinals) {
|
||||
super(parentWeight);
|
||||
this.parentWeight = parentWeight;
|
||||
this.globalOrdinals = globalOrdinals;
|
||||
this.parentsIterator = parentsIterator;
|
||||
this.parentIds = collector.parentIdxs;
|
||||
this.scores = ((ParentScoreCollector) collector).scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
return currentScore;
|
||||
}
|
||||
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
// We don't have the original child query hit info here...
|
||||
// But the freq of the children could be collector and returned here, but makes this Scorer more expensive.
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return currentDocId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
if (parentWeight.remaining == 0) {
|
||||
return currentDocId = NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
currentDocId = parentsIterator.nextDoc();
|
||||
if (currentDocId == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentDocId;
|
||||
}
|
||||
|
||||
final int globalOrdinal = globalOrdinals.getOrd(currentDocId);
|
||||
if (globalOrdinal < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
parentWeight.remaining--;
|
||||
if (acceptAndScore(parentIdx)) {
|
||||
return currentDocId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
if (parentWeight.remaining == 0) {
|
||||
return currentDocId = NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
currentDocId = parentsIterator.advance(target);
|
||||
if (currentDocId == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentDocId;
|
||||
}
|
||||
|
||||
final long globalOrdinal = globalOrdinals.getOrd(currentDocId);
|
||||
if (globalOrdinal < 0) {
|
||||
return nextDoc();
|
||||
}
|
||||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
parentWeight.remaining--;
|
||||
if (acceptAndScore(parentIdx)) {
|
||||
return currentDocId;
|
||||
}
|
||||
}
|
||||
return nextDoc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return parentsIterator.cost();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ParentCountScorer extends ParentScorer {
|
||||
|
||||
protected final IntArray occurrences;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
|
||||
ParentCountScorer(ParentWeight parentWeight, DocIdSetIterator parentsIterator, ParentCollector collector, SortedDocValues globalOrdinals, int minChildren, int maxChildren) {
|
||||
super(parentWeight, parentsIterator, (ParentScoreCollector) collector, globalOrdinals);
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
this.occurrences = ((ParentScoreCountCollector) collector).occurrences;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count < minChildren || count > maxChildren) {
|
||||
return false;
|
||||
}
|
||||
return super.acceptAndScore(parentIdx);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AvgParentScorer extends ParentCountScorer {
|
||||
|
||||
AvgParentScorer(ParentWeight weight, DocIdSetIterator parentsIterator, ParentCollector collector, SortedDocValues globalOrdinals) {
|
||||
super(weight, parentsIterator, collector, globalOrdinals, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class AvgParentCountScorer extends ParentCountScorer {
|
||||
|
||||
AvgParentCountScorer(ParentWeight weight, DocIdSetIterator parentsIterator, ParentCollector collector, SortedDocValues globalOrdinals, int minChildren, int maxChildren) {
|
||||
super(weight, parentsIterator, collector, globalOrdinals, minChildren, maxChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count < minChildren || count > maxChildren) {
|
||||
return false;
|
||||
}
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CountParentOrdIterator extends XFilteredDocIdSetIterator {
|
||||
|
||||
private final LongHash parentIds;
|
||||
protected final IntArray occurrences;
|
||||
private final int minChildren;
|
||||
private final int maxChildren;
|
||||
private final SortedDocValues ordinals;
|
||||
private final ParentWeight parentWeight;
|
||||
|
||||
private CountParentOrdIterator(ParentWeight parentWeight, DocIdSetIterator innerIterator, ParentCollector collector, SortedDocValues ordinals, int minChildren, int maxChildren) {
|
||||
super(innerIterator);
|
||||
this.parentIds = ((CountCollector) collector).parentIdxs;
|
||||
this.occurrences = ((CountCollector) collector).occurrences;
|
||||
this.ordinals = ordinals;
|
||||
this.parentWeight = parentWeight;
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(int doc) {
|
||||
if (parentWeight.remaining == 0) {
|
||||
throw new CollectionTerminatedException();
|
||||
}
|
||||
|
||||
final long parentOrd = ordinals.getOrd(doc);
|
||||
if (parentOrd >= 0) {
|
||||
final long parentIdx = parentIds.find(parentOrd);
|
||||
if (parentIdx != -1) {
|
||||
parentWeight.remaining--;
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count >= minChildren && count <= maxChildren) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A scorer that wraps a {@link DocIdSetIterator} and emits a constant score.
|
||||
*/
|
||||
// Borrowed from ConstantScoreQuery
|
||||
public class ConstantScorer extends Scorer {
|
||||
|
||||
public static ConstantScorer create(DocIdSetIterator iterator, Weight weight, float constantScore) throws IOException {
|
||||
return new ConstantScorer(iterator, weight, constantScore);
|
||||
}
|
||||
|
||||
private final DocIdSetIterator docIdSetIterator;
|
||||
private final float constantScore;
|
||||
|
||||
private ConstantScorer(DocIdSetIterator docIdSetIterator, Weight w, float constantScore) {
|
||||
super(w);
|
||||
this.constantScore = constantScore;
|
||||
this.docIdSetIterator = docIdSetIterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
return docIdSetIterator.nextDoc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return docIdSetIterator.docID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
assert docIdSetIterator.docID() != NO_MORE_DOCS;
|
||||
return constantScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
return docIdSetIterator.advance(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return docIdSetIterator.cost();
|
||||
}
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BitsFilteredDocIdSet;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.FilteredDocIdSetIterator;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.LongBitSet;
|
||||
import org.elasticsearch.common.lucene.IndexCacheableQuery;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.lucene.search.NoopCollector;
|
||||
import org.elasticsearch.index.fielddata.AtomicParentChildFieldData;
|
||||
import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A query that only return child documents that are linked to the parent documents that matched with the inner query.
|
||||
*/
|
||||
public class ParentConstantScoreQuery extends IndexCacheableQuery {
|
||||
|
||||
private final ParentChildIndexFieldData parentChildIndexFieldData;
|
||||
private Query parentQuery;
|
||||
private final String parentType;
|
||||
private final Filter childrenFilter;
|
||||
|
||||
public ParentConstantScoreQuery(ParentChildIndexFieldData parentChildIndexFieldData, Query parentQuery, String parentType, Filter childrenFilter) {
|
||||
this.parentChildIndexFieldData = parentChildIndexFieldData;
|
||||
this.parentQuery = parentQuery;
|
||||
this.parentType = parentType;
|
||||
this.childrenFilter = childrenFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) throws IOException {
|
||||
Query parentRewritten = parentQuery.rewrite(reader);
|
||||
if (parentRewritten != parentQuery) {
|
||||
Query rewritten = new ParentConstantScoreQuery(parentChildIndexFieldData, parentRewritten, parentType, childrenFilter);
|
||||
rewritten.setBoost(getBoost());
|
||||
return rewritten;
|
||||
}
|
||||
return super.rewrite(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight doCreateWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
IndexParentChildFieldData globalIfd = parentChildIndexFieldData.loadGlobal(searcher.getIndexReader());
|
||||
|
||||
final long maxOrd;
|
||||
List<LeafReaderContext> leaves = searcher.getIndexReader().leaves();
|
||||
if (globalIfd == null || leaves.isEmpty()) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
} else {
|
||||
AtomicParentChildFieldData afd = globalIfd.load(leaves.get(0));
|
||||
SortedDocValues globalValues = afd.getOrdinalsValues(parentType);
|
||||
maxOrd = globalValues.getValueCount();
|
||||
}
|
||||
|
||||
if (maxOrd == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
ParentOrdsCollector collector = new ParentOrdsCollector(globalIfd, maxOrd, parentType);
|
||||
searcher.search(parentQuery, collector);
|
||||
|
||||
if (collector.parentCount() == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
return new ChildrenWeight(this, childrenFilter, collector, globalIfd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + parentQuery.hashCode();
|
||||
result = 31 * result + parentType.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (super.equals(obj) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParentConstantScoreQuery that = (ParentConstantScoreQuery) obj;
|
||||
if (!parentQuery.equals(that.parentQuery)) {
|
||||
return false;
|
||||
}
|
||||
if (!parentType.equals(that.parentType)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return "parent_filter[" + parentType + "](" + parentQuery + ')';
|
||||
}
|
||||
|
||||
private final class ChildrenWeight extends Weight {
|
||||
|
||||
private final IndexParentChildFieldData globalIfd;
|
||||
private final Filter childrenFilter;
|
||||
private final LongBitSet parentOrds;
|
||||
|
||||
private float queryNorm;
|
||||
private float queryWeight;
|
||||
|
||||
private ChildrenWeight(Query query, Filter childrenFilter, ParentOrdsCollector collector, IndexParentChildFieldData globalIfd) {
|
||||
super(query);
|
||||
this.globalIfd = globalIfd;
|
||||
this.childrenFilter = childrenFilter;
|
||||
this.parentOrds = collector.parentOrds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractTerms(Set<Term> terms) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
|
||||
return Explanation.match(getBoost(), "not implemented yet...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getValueForNormalization() throws IOException {
|
||||
queryWeight = getBoost();
|
||||
return queryWeight * queryWeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
this.queryNorm = norm * topLevelBoost;
|
||||
queryWeight *= this.queryNorm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
DocIdSet childrenDocIdSet = childrenFilter.getDocIdSet(context, null);
|
||||
if (Lucene.isEmpty(childrenDocIdSet)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SortedDocValues globalValues = globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
if (globalValues != null) {
|
||||
// we forcefully apply live docs here so that deleted children don't give matching parents
|
||||
childrenDocIdSet = BitsFilteredDocIdSet.wrap(childrenDocIdSet, context.reader().getLiveDocs());
|
||||
DocIdSetIterator innerIterator = childrenDocIdSet.iterator();
|
||||
if (innerIterator != null) {
|
||||
ChildrenDocIdIterator childrenDocIdIterator = new ChildrenDocIdIterator(
|
||||
innerIterator, parentOrds, globalValues
|
||||
);
|
||||
return ConstantScorer.create(childrenDocIdIterator, this, queryWeight);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class ChildrenDocIdIterator extends FilteredDocIdSetIterator {
|
||||
|
||||
private final LongBitSet parentOrds;
|
||||
private final SortedDocValues globalOrdinals;
|
||||
|
||||
ChildrenDocIdIterator(DocIdSetIterator innerIterator, LongBitSet parentOrds, SortedDocValues globalOrdinals) {
|
||||
super(innerIterator);
|
||||
this.parentOrds = parentOrds;
|
||||
this.globalOrdinals = globalOrdinals;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(int docId) {
|
||||
int globalOrd = globalOrdinals.getOrd(docId);
|
||||
if (globalOrd >= 0) {
|
||||
return parentOrds.get(globalOrd);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static class ParentOrdsCollector extends NoopCollector {
|
||||
|
||||
private final LongBitSet parentOrds;
|
||||
private final IndexParentChildFieldData globalIfd;
|
||||
private final String parentType;
|
||||
|
||||
private SortedDocValues globalOrdinals;
|
||||
|
||||
ParentOrdsCollector(IndexParentChildFieldData globalIfd, long maxOrd, String parentType) {
|
||||
this.parentOrds = new LongBitSet(maxOrd);
|
||||
this.globalIfd = globalIfd;
|
||||
this.parentType = parentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
// It can happen that for particular segment no document exist for an specific type. This prevents NPE
|
||||
if (globalOrdinals != null) {
|
||||
long globalOrd = globalOrdinals.getOrd(doc);
|
||||
if (globalOrd >= 0) {
|
||||
parentOrds.set(globalOrd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetNextReader(LeafReaderContext readerContext) throws IOException {
|
||||
globalOrdinals = globalIfd.load(readerContext).getOrdinalsValues(parentType);
|
||||
}
|
||||
|
||||
public long parentCount() {
|
||||
return parentOrds.cardinality();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.PostingsEnum;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.index.Terms;
|
||||
import org.apache.lucene.index.TermsEnum;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.join.BitSetProducer;
|
||||
import org.apache.lucene.util.BitDocIdSet;
|
||||
import org.apache.lucene.util.BitSet;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.LongBitSet;
|
||||
import org.apache.lucene.util.SparseFixedBitSet;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.util.BytesRefHash;
|
||||
import org.elasticsearch.common.util.LongHash;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Advantages over using this filter over Lucene's TermsFilter in the parent child context:
|
||||
* 1) Don't need to copy all values over to a list from the id cache and then
|
||||
* copy all the ids values over to one continuous byte array. Should save a lot of of object creations and gcs..
|
||||
* 2) We filter docs by one field only.
|
||||
*/
|
||||
final class ParentIdsFilter extends Filter {
|
||||
|
||||
static Filter createShortCircuitFilter(BitSetProducer nonNestedDocsFilter, SearchContext searchContext,
|
||||
String parentType, SortedDocValues globalValues,
|
||||
LongBitSet parentOrds, long numFoundParents) {
|
||||
BytesRefHash parentIds= null;
|
||||
boolean constructed = false;
|
||||
try {
|
||||
parentIds = new BytesRefHash(numFoundParents, searchContext.bigArrays());
|
||||
for (long parentOrd = parentOrds.nextSetBit(0); parentOrd != -1; parentOrd = parentOrds.nextSetBit(parentOrd + 1)) {
|
||||
parentIds.add(globalValues.lookupOrd((int) parentOrd));
|
||||
}
|
||||
constructed = true;
|
||||
} finally {
|
||||
if (!constructed) {
|
||||
Releasables.close(parentIds);
|
||||
}
|
||||
}
|
||||
searchContext.addReleasable(parentIds, SearchContext.Lifetime.COLLECTION);
|
||||
return new ParentIdsFilter(parentType, nonNestedDocsFilter, parentIds);
|
||||
}
|
||||
|
||||
static Filter createShortCircuitFilter(BitSetProducer nonNestedDocsFilter, SearchContext searchContext,
|
||||
String parentType, SortedDocValues globalValues,
|
||||
LongHash parentIdxs, long numFoundParents) {
|
||||
BytesRefHash parentIds = null;
|
||||
boolean constructed = false;
|
||||
try {
|
||||
parentIds = new BytesRefHash(numFoundParents, searchContext.bigArrays());
|
||||
for (int id = 0; id < parentIdxs.size(); id++) {
|
||||
parentIds.add(globalValues.lookupOrd((int) parentIdxs.get(id)));
|
||||
}
|
||||
constructed = true;
|
||||
} finally {
|
||||
if (!constructed) {
|
||||
Releasables.close(parentIds);
|
||||
}
|
||||
}
|
||||
searchContext.addReleasable(parentIds, SearchContext.Lifetime.COLLECTION);
|
||||
return new ParentIdsFilter(parentType, nonNestedDocsFilter, parentIds);
|
||||
}
|
||||
|
||||
private final BytesRef parentTypeBr;
|
||||
private final BitSetProducer nonNestedDocsFilter;
|
||||
private final BytesRefHash parentIds;
|
||||
|
||||
private ParentIdsFilter(String parentType, BitSetProducer nonNestedDocsFilter, BytesRefHash parentIds) {
|
||||
this.nonNestedDocsFilter = nonNestedDocsFilter;
|
||||
this.parentTypeBr = new BytesRef(parentType);
|
||||
this.parentIds = parentIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSet getDocIdSet(LeafReaderContext context, Bits acceptDocs) throws IOException {
|
||||
Terms terms = context.reader().terms(UidFieldMapper.NAME);
|
||||
if (terms == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TermsEnum termsEnum = terms.iterator();
|
||||
BytesRefBuilder uidSpare = new BytesRefBuilder();
|
||||
BytesRef idSpare = new BytesRef();
|
||||
|
||||
if (acceptDocs == null) {
|
||||
acceptDocs = context.reader().getLiveDocs();
|
||||
}
|
||||
|
||||
BitSet nonNestedDocs = null;
|
||||
if (nonNestedDocsFilter != null) {
|
||||
nonNestedDocs = nonNestedDocsFilter.getBitSet(context);
|
||||
}
|
||||
|
||||
PostingsEnum docsEnum = null;
|
||||
BitSet result = null;
|
||||
int size = (int) parentIds.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
parentIds.get(i, idSpare);
|
||||
BytesRef uid = Uid.createUidAsBytes(parentTypeBr, idSpare, uidSpare);
|
||||
if (termsEnum.seekExact(uid)) {
|
||||
docsEnum = termsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
int docId;
|
||||
for (docId = docsEnum.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = docsEnum.nextDoc()) {
|
||||
if (acceptDocs == null || acceptDocs.get(docId)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (docId == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
continue;
|
||||
}
|
||||
if (result == null) {
|
||||
// very rough heuristic that tries to get an idea of the number of documents
|
||||
// in the set based on the number of parent ids that we didn't find in this segment
|
||||
final int expectedCardinality = size / (i + 1);
|
||||
// similar heuristic to BitDocIdSet.Builder
|
||||
if (expectedCardinality >= (context.reader().maxDoc() >>> 10)) {
|
||||
result = new FixedBitSet(context.reader().maxDoc());
|
||||
} else {
|
||||
result = new SparseFixedBitSet(context.reader().maxDoc());
|
||||
}
|
||||
}
|
||||
if (nonNestedDocs != null) {
|
||||
docId = nonNestedDocs.nextSetBit(docId);
|
||||
}
|
||||
result.set(docId);
|
||||
assert docsEnum.advance(docId + 1) == DocIdSetIterator.NO_MORE_DOCS : "DocId " + docId + " should have been the last one but docId " + docsEnum.docID() + " exists.";
|
||||
}
|
||||
}
|
||||
return result == null ? null : new BitDocIdSet(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return "parentsFilter(type=" + parentTypeBr.utf8ToString() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (super.equals(obj) == false) {
|
||||
return false;
|
||||
}
|
||||
ParentIdsFilter other = (ParentIdsFilter) obj;
|
||||
return parentTypeBr.equals(other.parentTypeBr)
|
||||
&& parentIds.equals(other.parentIds)
|
||||
&& nonNestedDocsFilter.equals(nonNestedDocsFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = super.hashCode();
|
||||
h = 31 * h + parentTypeBr.hashCode();
|
||||
h = 31 * h + parentIds.hashCode();
|
||||
h = 31 * h + nonNestedDocsFilter.hashCode();
|
||||
return h;
|
||||
}
|
||||
}
|
|
@ -1,350 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.index.SortedSetDocValues;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BitsFilteredDocIdSet;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.Collector;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.LeafCollector;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.ToStringUtils;
|
||||
import org.elasticsearch.common.lease.Releasable;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.lucene.IndexCacheableQuery;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.lucene.search.NoopCollector;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.common.util.FloatArray;
|
||||
import org.elasticsearch.common.util.LongHash;
|
||||
import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.internal.SearchContext.Lifetime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A query implementation that executes the wrapped parent query and
|
||||
* connects the matching parent docs to the related child documents
|
||||
* using the {@link ParentChildIndexFieldData}.
|
||||
*/
|
||||
public class ParentQuery extends IndexCacheableQuery {
|
||||
|
||||
private final ParentChildIndexFieldData parentChildIndexFieldData;
|
||||
private Query parentQuery;
|
||||
private final String parentType;
|
||||
private final Filter childrenFilter;
|
||||
|
||||
public ParentQuery(ParentChildIndexFieldData parentChildIndexFieldData, Query parentQuery, String parentType, Filter childrenFilter) {
|
||||
this.parentChildIndexFieldData = parentChildIndexFieldData;
|
||||
this.parentQuery = parentQuery;
|
||||
this.parentType = parentType;
|
||||
this.childrenFilter = childrenFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (super.equals(obj) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParentQuery that = (ParentQuery) obj;
|
||||
if (!parentQuery.equals(that.parentQuery)) {
|
||||
return false;
|
||||
}
|
||||
if (!parentType.equals(that.parentType)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + parentQuery.hashCode();
|
||||
result = 31 * result + parentType.hashCode();
|
||||
result = 31 * result + Float.floatToIntBits(getBoost());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return "ParentQuery[" + parentType + "](" + parentQuery.toString(field) + ')' + ToStringUtils.boost(getBoost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) throws IOException {
|
||||
Query parentRewritten = parentQuery.rewrite(reader);
|
||||
if (parentRewritten != parentQuery) {
|
||||
Query rewritten = new ParentQuery(parentChildIndexFieldData, parentRewritten, parentType, childrenFilter);
|
||||
rewritten.setBoost(getBoost());
|
||||
return rewritten;
|
||||
}
|
||||
return super.rewrite(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight doCreateWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
SearchContext sc = SearchContext.current();
|
||||
ChildWeight childWeight;
|
||||
boolean releaseCollectorResource = true;
|
||||
ParentOrdAndScoreCollector collector = null;
|
||||
IndexParentChildFieldData globalIfd = parentChildIndexFieldData.loadGlobal(searcher.getIndexReader());
|
||||
if (globalIfd == null) {
|
||||
// No docs of the specified type don't exist on this shard
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
|
||||
try {
|
||||
collector = new ParentOrdAndScoreCollector(sc, globalIfd, parentType);
|
||||
searcher.search(parentQuery, collector);
|
||||
if (collector.parentCount() == 0) {
|
||||
return new BooleanQuery.Builder().build().createWeight(searcher, needsScores);
|
||||
}
|
||||
childWeight = new ChildWeight(this, parentQuery.createWeight(searcher, needsScores), childrenFilter, collector, globalIfd);
|
||||
releaseCollectorResource = false;
|
||||
} finally {
|
||||
if (releaseCollectorResource) {
|
||||
// either if we run into an exception or if we return early
|
||||
Releasables.close(collector);
|
||||
}
|
||||
}
|
||||
sc.addReleasable(collector, Lifetime.COLLECTION);
|
||||
return childWeight;
|
||||
}
|
||||
|
||||
private static class ParentOrdAndScoreCollector implements Collector, Releasable {
|
||||
|
||||
private final LongHash parentIdxs;
|
||||
private FloatArray scores;
|
||||
private final IndexParentChildFieldData globalIfd;
|
||||
private final BigArrays bigArrays;
|
||||
private final String parentType;
|
||||
|
||||
ParentOrdAndScoreCollector(SearchContext searchContext, IndexParentChildFieldData globalIfd, String parentType) {
|
||||
this.bigArrays = searchContext.bigArrays();
|
||||
this.parentIdxs = new LongHash(512, bigArrays);
|
||||
this.scores = bigArrays.newFloatArray(512, false);
|
||||
this.globalIfd = globalIfd;
|
||||
this.parentType = parentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsScores() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
|
||||
final SortedDocValues values = globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
if (values == null) {
|
||||
return NoopCollector.NOOP_COLLECTOR;
|
||||
}
|
||||
return new LeafCollector() {
|
||||
Scorer scorer;
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) throws IOException {
|
||||
this.scorer = scorer;
|
||||
}
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
long globalOrdinal = values.getOrd(doc);
|
||||
if (globalOrdinal != SortedSetDocValues.NO_MORE_ORDS) {
|
||||
long parentIdx = parentIdxs.add(globalOrdinal);
|
||||
if (parentIdx >= 0) {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
} else {
|
||||
assert false : "parent id should only match once, since there can only be one parent doc";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Releasables.close(parentIdxs, scores);
|
||||
}
|
||||
|
||||
public long parentCount() {
|
||||
return parentIdxs.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ChildWeight extends Weight {
|
||||
|
||||
private final Weight parentWeight;
|
||||
private final Filter childrenFilter;
|
||||
private final LongHash parentIdxs;
|
||||
private final FloatArray scores;
|
||||
private final IndexParentChildFieldData globalIfd;
|
||||
|
||||
private ChildWeight(Query query, Weight parentWeight, Filter childrenFilter, ParentOrdAndScoreCollector collector, IndexParentChildFieldData globalIfd) {
|
||||
super(query);
|
||||
this.parentWeight = parentWeight;
|
||||
this.childrenFilter = childrenFilter;
|
||||
this.parentIdxs = collector.parentIdxs;
|
||||
this.scores = collector.scores;
|
||||
this.globalIfd = globalIfd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractTerms(Set<Term> terms) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
|
||||
return Explanation.match(getBoost(), "not implemented yet...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getValueForNormalization() throws IOException {
|
||||
float sum = parentWeight.getValueForNormalization();
|
||||
sum *= getBoost() * getBoost();
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
DocIdSet childrenDocSet = childrenFilter.getDocIdSet(context, null);
|
||||
// we forcefully apply live docs here so that deleted children don't give matching parents
|
||||
childrenDocSet = BitsFilteredDocIdSet.wrap(childrenDocSet, context.reader().getLiveDocs());
|
||||
if (Lucene.isEmpty(childrenDocSet)) {
|
||||
return null;
|
||||
}
|
||||
final DocIdSetIterator childIterator = childrenDocSet.iterator();
|
||||
if (childIterator == null) {
|
||||
return null;
|
||||
}
|
||||
SortedDocValues bytesValues = globalIfd.load(context).getOrdinalsValues(parentType);
|
||||
if (bytesValues == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChildScorer(this, parentIdxs, scores, childIterator, bytesValues);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ChildScorer extends Scorer {
|
||||
|
||||
private final LongHash parentIdxs;
|
||||
private final FloatArray scores;
|
||||
private final DocIdSetIterator childrenIterator;
|
||||
private final SortedDocValues ordinals;
|
||||
|
||||
private int currentChildDoc = -1;
|
||||
private float currentScore;
|
||||
|
||||
ChildScorer(Weight weight, LongHash parentIdxs, FloatArray scores, DocIdSetIterator childrenIterator, SortedDocValues ordinals) {
|
||||
super(weight);
|
||||
this.parentIdxs = parentIdxs;
|
||||
this.scores = scores;
|
||||
this.childrenIterator = childrenIterator;
|
||||
this.ordinals = ordinals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
return currentScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
// We don't have the original child query hit info here...
|
||||
// But the freq of the children could be collector and returned here, but makes this Scorer more expensive.
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return currentChildDoc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
while (true) {
|
||||
currentChildDoc = childrenIterator.nextDoc();
|
||||
if (currentChildDoc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentChildDoc;
|
||||
}
|
||||
|
||||
int globalOrdinal = (int) ordinals.getOrd(currentChildDoc);
|
||||
if (globalOrdinal < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final long parentIdx = parentIdxs.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
return currentChildDoc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
currentChildDoc = childrenIterator.advance(target);
|
||||
if (currentChildDoc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentChildDoc;
|
||||
}
|
||||
|
||||
int globalOrdinal = (int) ordinals.getOrd(currentChildDoc);
|
||||
if (globalOrdinal < 0) {
|
||||
return nextDoc();
|
||||
}
|
||||
|
||||
final long parentIdx = parentIdxs.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
return currentChildDoc;
|
||||
} else {
|
||||
return nextDoc();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return childrenIterator.cost();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,16 +20,12 @@ package org.elasticsearch.search.aggregations.bucket.children;
|
|||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.SortedDocValues;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.util.LongArray;
|
||||
import org.elasticsearch.common.util.LongObjectPagedHashMap;
|
||||
import org.elasticsearch.index.search.child.ConstantScorer;
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregation;
|
||||
|
@ -155,7 +151,7 @@ public class ParentToChildrenAggregator extends SingleBucketAggregator {
|
|||
final SortedDocValues globalOrdinals = valuesSource.globalOrdinalsValues(parentType, ctx);
|
||||
|
||||
// Set the scorer, since we now replay only the child docIds
|
||||
sub.setScorer(ConstantScorer.create(childDocsIter, null, 1f));
|
||||
sub.setScorer(new ConstantScoreScorer(null, 1f,childDocsIter));
|
||||
|
||||
final Bits liveDocs = ctx.reader().getLiveDocs();
|
||||
for (int docId = childDocsIter.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = childDocsIter.nextDoc()) {
|
||||
|
|
|
@ -979,30 +979,6 @@ public class IndexAliasesIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAliasesFilterWithHasChildQueryPre2Dot0() throws Exception {
|
||||
assertAcked(prepareCreate("my-index")
|
||||
.setSettings(Settings.builder()
|
||||
.put(indexSettings())
|
||||
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_6_0)
|
||||
)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", "_parent", "type=parent")
|
||||
);
|
||||
client().prepareIndex("my-index", "parent", "1").setSource("{}").get();
|
||||
client().prepareIndex("my-index", "child", "2").setSource("{}").setParent("1").get();
|
||||
refresh();
|
||||
|
||||
assertAcked(admin().indices().prepareAliases().addAlias("my-index", "filter1", hasChildQuery("child", matchAllQuery())));
|
||||
assertAcked(admin().indices().prepareAliases().addAlias("my-index", "filter2", hasParentQuery("parent", matchAllQuery())));
|
||||
SearchResponse response = client().prepareSearch("filter1").get();
|
||||
assertHitCount(response, 1);
|
||||
assertThat(response.getHits().getAt(0).id(), equalTo("1"));
|
||||
response = client().prepareSearch("filter2").get();
|
||||
assertHitCount(response, 1);
|
||||
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAliasesWithBlocks() {
|
||||
createIndex("test");
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* 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.fielddata.plain;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.core.IsNull.notNullValue;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ParentChildFilteredTermsEnumTests extends ESTestCase {
|
||||
|
||||
@Test
|
||||
public void testSimple_twoFieldEachUniqueValue() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory);
|
||||
for (int i = 1; i <= 10000; i++) {
|
||||
Document document = new Document();
|
||||
String fieldName = i % 2 == 0 ? "field1" : "field2";
|
||||
document.add(new StringField(fieldName, format(i), Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(indexWriter.w, false);
|
||||
TermsEnum[] compoundTermsEnums = new TermsEnum[]{
|
||||
new ParentChildIntersectTermsEnum(SlowCompositeReaderWrapper.wrap(indexReader), "field1", "field2")
|
||||
};
|
||||
for (TermsEnum termsEnum : compoundTermsEnums) {
|
||||
int expected = 0;
|
||||
for (BytesRef term = termsEnum.next(); term != null; term = termsEnum.next()) {
|
||||
++expected;
|
||||
assertThat(term.utf8ToString(), equalTo(format(expected)));
|
||||
PostingsEnum docsEnum = termsEnum.postings(null);
|
||||
assertThat(docsEnum, notNullValue());
|
||||
int docId = docsEnum.nextDoc();
|
||||
assertThat(docId, not(equalTo(-1)));
|
||||
assertThat(docId, not(equalTo(DocIdSetIterator.NO_MORE_DOCS)));
|
||||
assertThat(docsEnum.nextDoc(), equalTo(DocIdSetIterator.NO_MORE_DOCS));
|
||||
}
|
||||
}
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocument_twoFieldsEachSharingValues() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory);
|
||||
for (int i = 1; i <= 1000; i++) {
|
||||
Document document = new Document();
|
||||
document.add(new StringField("field1", format(i), Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
document = new Document();
|
||||
document.add(new StringField("field2", format(i), Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(indexWriter.w, false);
|
||||
TermsEnum[] compoundTermsEnums = new TermsEnum[]{
|
||||
new ParentChildIntersectTermsEnum(SlowCompositeReaderWrapper.wrap(indexReader), "field1", "field2")
|
||||
};
|
||||
for (TermsEnum termsEnum : compoundTermsEnums) {
|
||||
int expected = 0;
|
||||
for (BytesRef term = termsEnum.next(); term != null; term = termsEnum.next()) {
|
||||
++expected;
|
||||
assertThat(term.utf8ToString(), equalTo(format(expected)));
|
||||
PostingsEnum docsEnum = termsEnum.postings(null);
|
||||
assertThat(docsEnum, notNullValue());
|
||||
int numDocs = 0;
|
||||
for (int docId = docsEnum.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = docsEnum.nextDoc()) {
|
||||
numDocs++;
|
||||
}
|
||||
assertThat(numDocs, equalTo(11));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
static String format(int i) {
|
||||
return String.format(Locale.ROOT, "%06d", i);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.index.search.child;
|
||||
package org.elasticsearch.index.query;
|
||||
|
||||
import org.elasticsearch.index.query.ScoreType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Test;
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.search.join.BitSetProducer;
|
||||
import org.apache.lucene.util.BitDocIdSet;
|
||||
import org.apache.lucene.util.BitSet;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.common.compress.CompressedXContent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.IndexService;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.StringDescription;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public abstract class AbstractChildTestCase extends ESSingleNodeTestCase {
|
||||
|
||||
/**
|
||||
* The name of the field within the child type that stores a score to use in test queries.
|
||||
* <p />
|
||||
* Its type is {@code double}.
|
||||
*/
|
||||
protected static String CHILD_SCORE_NAME = "childScore";
|
||||
|
||||
static SearchContext createSearchContext(String indexName, String parentType, String childType) throws IOException {
|
||||
Settings settings = Settings.builder()
|
||||
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_6_0)
|
||||
.build();
|
||||
IndexService indexService = createIndex(indexName, settings);
|
||||
MapperService mapperService = indexService.mapperService();
|
||||
// Parent/child parsers require that the parent and child type to be presented in mapping
|
||||
// Sometimes we want a nested object field in the parent type that triggers nonNestedDocsFilter to be used
|
||||
mapperService.merge(parentType, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(parentType, "nested_field", random().nextBoolean() ? "type=nested" : "type=object").string()), true, false);
|
||||
mapperService.merge(childType, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(childType, "_parent", "type=" + parentType, CHILD_SCORE_NAME, "type=double,doc_values=false").string()), true, false);
|
||||
return createSearchContext(indexService);
|
||||
}
|
||||
|
||||
static void assertBitSet(BitSet actual, BitSet expected, IndexSearcher searcher) throws IOException {
|
||||
assertBitSet(new BitDocIdSet(actual), new BitDocIdSet(expected), searcher);
|
||||
}
|
||||
|
||||
static void assertBitSet(BitDocIdSet actual, BitDocIdSet expected, IndexSearcher searcher) throws IOException {
|
||||
if (!equals(expected, actual)) {
|
||||
Description description = new StringDescription();
|
||||
description.appendText(reason(actual, expected, searcher));
|
||||
description.appendText("\nExpected: ");
|
||||
description.appendValue(expected);
|
||||
description.appendText("\n got: ");
|
||||
description.appendValue(actual);
|
||||
description.appendText("\n");
|
||||
throw new java.lang.AssertionError(description.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static boolean equals(BitDocIdSet expected, BitDocIdSet actual) {
|
||||
if (actual == null && expected == null) {
|
||||
return true;
|
||||
} else if (actual == null || expected == null) {
|
||||
return false;
|
||||
}
|
||||
BitSet actualBits = actual.bits();
|
||||
BitSet expectedBits = expected.bits();
|
||||
if (actualBits.length() != expectedBits.length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < expectedBits.length(); i++) {
|
||||
if (expectedBits.get(i) != actualBits.get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static String reason(BitDocIdSet actual, BitDocIdSet expected, IndexSearcher indexSearcher) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("expected cardinality:").append(expected.bits().cardinality()).append('\n');
|
||||
DocIdSetIterator iterator = expected.iterator();
|
||||
for (int doc = iterator.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = iterator.nextDoc()) {
|
||||
builder.append("Expected doc[").append(doc).append("] with id value ").append(indexSearcher.doc(doc).get(UidFieldMapper.NAME)).append('\n');
|
||||
}
|
||||
builder.append("actual cardinality: ").append(actual.bits().cardinality()).append('\n');
|
||||
iterator = actual.iterator();
|
||||
for (int doc = iterator.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = iterator.nextDoc()) {
|
||||
builder.append("Actual doc[").append(doc).append("] with id value ").append(indexSearcher.doc(doc).get(UidFieldMapper.NAME)).append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static void assertTopDocs(TopDocs actual, TopDocs expected) {
|
||||
assertThat("actual.totalHits != expected.totalHits", actual.totalHits, equalTo(expected.totalHits));
|
||||
assertThat("actual.getMaxScore() != expected.getMaxScore()", actual.getMaxScore(), equalTo(expected.getMaxScore()));
|
||||
assertThat("actual.scoreDocs.length != expected.scoreDocs.length", actual.scoreDocs.length, equalTo(actual.scoreDocs.length));
|
||||
for (int i = 0; i < actual.scoreDocs.length; i++) {
|
||||
ScoreDoc actualHit = actual.scoreDocs[i];
|
||||
ScoreDoc expectedHit = expected.scoreDocs[i];
|
||||
assertThat("actualHit.doc != expectedHit.doc", actualHit.doc, equalTo(expectedHit.doc));
|
||||
assertThat("actualHit.score != expectedHit.score", actualHit.score, equalTo(expectedHit.score));
|
||||
}
|
||||
}
|
||||
|
||||
static BitSetProducer wrapWithBitSetFilter(Filter filter) {
|
||||
return SearchContext.current().bitsetFilterCache().getBitSetProducer(filter);
|
||||
}
|
||||
|
||||
static Query parseQuery(QueryBuilder queryBuilder) throws IOException {
|
||||
QueryParseContext context = new QueryParseContext(new Index("test"), SearchContext.current().queryParserService());
|
||||
XContentParser parser = XContentHelper.createParser(queryBuilder.buildAsBytes());
|
||||
context.reset(parser);
|
||||
return context.parseInnerQuery();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.elasticsearch.common.lucene.search.NoopCollector;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class BitSetCollector extends NoopCollector {
|
||||
|
||||
final FixedBitSet result;
|
||||
int docBase;
|
||||
|
||||
BitSetCollector(int topLevelMaxDoc) {
|
||||
this.result = new FixedBitSet(topLevelMaxDoc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
result.set(docBase + doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSetNextReader(LeafReaderContext context) throws IOException {
|
||||
docBase = context.docBase;
|
||||
}
|
||||
|
||||
FixedBitSet getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,295 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import com.carrotsearch.hppc.IntHashSet;
|
||||
import com.carrotsearch.hppc.ObjectObjectHashMap;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.QueryUtils;
|
||||
import org.apache.lucene.search.QueryWrapperFilter;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.lucene.search.Queries;
|
||||
import org.elasticsearch.index.engine.Engine;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.hasChildQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.notQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class ChildrenConstantScoreQueryTests extends AbstractChildTestCase {
|
||||
|
||||
@BeforeClass
|
||||
public static void before() throws IOException {
|
||||
SearchContext.setCurrent(createSearchContext("test", "parent", "child"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void after() throws IOException {
|
||||
SearchContext current = SearchContext.current();
|
||||
SearchContext.removeCurrent();
|
||||
Releasables.close(current);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicQuerySanities() {
|
||||
Query childQuery = new TermQuery(new Term("field", "value"));
|
||||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper.fieldType());
|
||||
Filter parentFilter = new QueryWrapperFilter(new TermQuery(new Term(TypeFieldMapper.NAME, "parent")));
|
||||
Query query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, childQuery, "parent", "child", parentFilter, 12, wrapWithBitSetFilter(Queries.newNonNestedFilter()));
|
||||
QueryUtils.check(query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory);
|
||||
|
||||
for (int parent = 1; parent <= 5; parent++) {
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", Integer.toString(parent)), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
for (int child = 1; child <= 3; child++) {
|
||||
document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", Integer.toString(parent * 3 + child)), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", Integer.toString(parent)), Field.Store.NO));
|
||||
document.add(new StringField("field1", "value" + child, Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(indexWriter.w, false);
|
||||
IndexSearcher searcher = new IndexSearcher(indexReader);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(
|
||||
new Engine.Searcher(ChildrenConstantScoreQueryTests.class.getSimpleName(), searcher)
|
||||
);
|
||||
|
||||
TermQuery childQuery = new TermQuery(new Term("field1", "value" + (1 + random().nextInt(3))));
|
||||
Filter parentFilter = new QueryWrapperFilter(new TermQuery(new Term(TypeFieldMapper.NAME, "parent")));
|
||||
int shortCircuitParentDocSet = random().nextInt(5);
|
||||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper.fieldType());
|
||||
ChildrenConstantScoreQuery query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet, null);
|
||||
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
searcher.search(query, collector);
|
||||
FixedBitSet actualResult = collector.getResult();
|
||||
|
||||
assertThat(actualResult.cardinality(), equalTo(5));
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
final Random r = random();
|
||||
final IndexWriterConfig iwc = LuceneTestCase.newIndexWriterConfig(r, new MockAnalyzer(r))
|
||||
.setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH)
|
||||
.setRAMBufferSizeMB(scaledRandomIntBetween(16, 64)); // we might index a lot - don't go crazy here
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(r, directory, iwc);
|
||||
int numUniqueChildValues = scaledRandomIntBetween(100, 2000);
|
||||
String[] childValues = new String[numUniqueChildValues];
|
||||
for (int i = 0; i < numUniqueChildValues; i++) {
|
||||
childValues[i] = Integer.toString(i);
|
||||
}
|
||||
|
||||
IntHashSet filteredOrDeletedDocs = new IntHashSet();
|
||||
int childDocId = 0;
|
||||
int numParentDocs = scaledRandomIntBetween(1, numUniqueChildValues);
|
||||
ObjectObjectHashMap<String, NavigableSet<String>> childValueToParentIds = new ObjectObjectHashMap<>();
|
||||
for (int parentDocId = 0; parentDocId < numParentDocs; parentDocId++) {
|
||||
boolean markParentAsDeleted = rarely();
|
||||
boolean filterMe = rarely();
|
||||
String parent = Integer.toString(parentDocId);
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
if (markParentAsDeleted) {
|
||||
filteredOrDeletedDocs.add(parentDocId);
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
if (filterMe) {
|
||||
filteredOrDeletedDocs.add(parentDocId);
|
||||
document.add(new StringField("filter", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
final int numChildDocs = scaledRandomIntBetween(0, 100);
|
||||
for (int i = 0; i < numChildDocs; i++) {
|
||||
boolean markChildAsDeleted = rarely();
|
||||
String childValue = childValues[random().nextInt(childValues.length)];
|
||||
|
||||
document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", Integer.toString(childDocId++)), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
document.add(new StringField("field1", childValue, Field.Store.NO));
|
||||
if (markChildAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
if (!markChildAsDeleted) {
|
||||
NavigableSet<String> parentIds;
|
||||
if (childValueToParentIds.containsKey(childValue)) {
|
||||
parentIds = childValueToParentIds.get(childValue);
|
||||
} else {
|
||||
childValueToParentIds.put(childValue, parentIds = new TreeSet<>());
|
||||
}
|
||||
if (!markParentAsDeleted && !filterMe) {
|
||||
parentIds.add(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete docs that are marked to be deleted.
|
||||
indexWriter.deleteDocuments(new Term("delete", "me"));
|
||||
|
||||
indexWriter.commit();
|
||||
IndexReader indexReader = DirectoryReader.open(directory);
|
||||
IndexSearcher searcher = new IndexSearcher(indexReader);
|
||||
Engine.Searcher engineSearcher = new Engine.Searcher(
|
||||
ChildrenConstantScoreQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
|
||||
int max = numUniqueChildValues / 4;
|
||||
for (int i = 0; i < max; i++) {
|
||||
// Simulate a parent update
|
||||
if (random().nextBoolean()) {
|
||||
final int numberOfUpdatableParents = numParentDocs - filteredOrDeletedDocs.size();
|
||||
int numberOfUpdates = scaledRandomIntBetween(0, numberOfUpdatableParents);
|
||||
for (int j = 0; j < numberOfUpdates; j++) {
|
||||
int parentId;
|
||||
do {
|
||||
parentId = random().nextInt(numParentDocs);
|
||||
} while (filteredOrDeletedDocs.contains(parentId));
|
||||
|
||||
String parentUid = Uid.createUid("parent", Integer.toString(parentId));
|
||||
indexWriter.deleteDocuments(new Term(UidFieldMapper.NAME, parentUid));
|
||||
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, parentUid, Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
|
||||
indexReader.close();
|
||||
indexReader = DirectoryReader.open(indexWriter.w, true);
|
||||
searcher = new IndexSearcher(indexReader);
|
||||
engineSearcher = new Engine.Searcher(
|
||||
ChildrenConstantScoreQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
}
|
||||
|
||||
String childValue = childValues[random().nextInt(numUniqueChildValues)];
|
||||
int shortCircuitParentDocSet = random().nextInt(numParentDocs);
|
||||
QueryBuilder queryBuilder;
|
||||
if (random().nextBoolean()) {
|
||||
queryBuilder = hasChildQuery("child", termQuery("field1", childValue))
|
||||
.setShortCircuitCutoff(shortCircuitParentDocSet);
|
||||
} else {
|
||||
queryBuilder = constantScoreQuery(
|
||||
hasChildQuery("child", termQuery("field1", childValue))
|
||||
.setShortCircuitCutoff(shortCircuitParentDocSet)
|
||||
);
|
||||
}
|
||||
// Using a FQ, will invoke / test the Scorer#advance(..) and also let the Weight#scorer not get live docs as acceptedDocs
|
||||
queryBuilder = filteredQuery(queryBuilder, notQuery(termQuery("filter", "me")));
|
||||
Query query = parseQuery(queryBuilder);
|
||||
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
searcher.search(query, collector);
|
||||
FixedBitSet actualResult = collector.getResult();
|
||||
|
||||
FixedBitSet expectedResult = new FixedBitSet(indexReader.maxDoc());
|
||||
if (childValueToParentIds.containsKey(childValue)) {
|
||||
LeafReader slowLeafReader = SlowCompositeReaderWrapper.wrap(indexReader);
|
||||
Terms terms = slowLeafReader.terms(UidFieldMapper.NAME);
|
||||
if (terms != null) {
|
||||
NavigableSet<String> parentIds = childValueToParentIds.get(childValue);
|
||||
TermsEnum termsEnum = terms.iterator();
|
||||
PostingsEnum docsEnum = null;
|
||||
for (String id : parentIds) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("parent", id));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
final Bits liveDocs = slowLeafReader.getLiveDocs();
|
||||
for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
|
||||
if (liveDocs == null || liveDocs.get(doc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
expectedResult.set(docsEnum.docID());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertBitSet(actualResult, expectedResult, searcher);
|
||||
}
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,397 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import com.carrotsearch.hppc.FloatArrayList;
|
||||
import com.carrotsearch.hppc.IntHashSet;
|
||||
import com.carrotsearch.hppc.ObjectObjectHashMap;
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomInts;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.DoubleField;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.lucene.search.Queries;
|
||||
import org.elasticsearch.index.engine.Engine;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.internal.IdFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.functionscore.fieldvaluefactor.FieldValueFactorFunctionBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
public class ChildrenQueryTests extends AbstractChildTestCase {
|
||||
|
||||
@BeforeClass
|
||||
public static void before() throws IOException {
|
||||
SearchContext.setCurrent(createSearchContext("test", "parent", "child"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void after() throws IOException {
|
||||
SearchContext current = SearchContext.current();
|
||||
SearchContext.removeCurrent();
|
||||
Releasables.close(current);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicQuerySanities() {
|
||||
Query childQuery = new TermQuery(new Term("field", "value"));
|
||||
ScoreType scoreType = ScoreType.values()[random().nextInt(ScoreType.values().length)];
|
||||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper.fieldType());
|
||||
Filter parentFilter = new QueryWrapperFilter(new TermQuery(new Term(TypeFieldMapper.NAME, "parent")));
|
||||
int minChildren = random().nextInt(10);
|
||||
int maxChildren = scaledRandomIntBetween(minChildren, 10);
|
||||
Query query = new ChildrenQuery(parentChildIndexFieldData, "parent", "child", parentFilter, childQuery, scoreType, minChildren,
|
||||
maxChildren, 12, wrapWithBitSetFilter(Queries.newNonNestedFilter()));
|
||||
QueryUtils.check(query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
final Random r = random();
|
||||
final IndexWriterConfig iwc = LuceneTestCase.newIndexWriterConfig(r, new MockAnalyzer(r))
|
||||
.setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH)
|
||||
.setRAMBufferSizeMB(scaledRandomIntBetween(16, 64)); // we might index a lot - don't go crazy here
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(r, directory, iwc);
|
||||
int numUniqueChildValues = scaledRandomIntBetween(100, 2000);
|
||||
String[] childValues = new String[numUniqueChildValues];
|
||||
for (int i = 0; i < numUniqueChildValues; i++) {
|
||||
childValues[i] = Integer.toString(i);
|
||||
}
|
||||
|
||||
IntHashSet filteredOrDeletedDocs = new IntHashSet();
|
||||
|
||||
int childDocId = 0;
|
||||
int numParentDocs = scaledRandomIntBetween(1, numUniqueChildValues);
|
||||
ObjectObjectHashMap<String, NavigableMap<String, FloatArrayList>> childValueToParentIds = new ObjectObjectHashMap<>();
|
||||
for (int parentDocId = 0; parentDocId < numParentDocs; parentDocId++) {
|
||||
boolean markParentAsDeleted = rarely();
|
||||
boolean filterMe = rarely();
|
||||
String parent = Integer.toString(parentDocId);
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
if (markParentAsDeleted) {
|
||||
filteredOrDeletedDocs.add(parentDocId);
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
if (filterMe) {
|
||||
filteredOrDeletedDocs.add(parentDocId);
|
||||
document.add(new StringField("filter", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
int numChildDocs = scaledRandomIntBetween(0, 100);
|
||||
for (int i = 0; i < numChildDocs; i++) {
|
||||
boolean markChildAsDeleted = rarely();
|
||||
String childValue = childValues[random().nextInt(childValues.length)];
|
||||
|
||||
document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", Integer.toString(childDocId++)), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
document.add(new StringField("field1", childValue, Field.Store.NO));
|
||||
if (markChildAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
if (!markChildAsDeleted) {
|
||||
NavigableMap<String, FloatArrayList> parentIdToChildScores;
|
||||
if (childValueToParentIds.containsKey(childValue)) {
|
||||
parentIdToChildScores = childValueToParentIds.get(childValue);
|
||||
} else {
|
||||
childValueToParentIds.put(childValue, parentIdToChildScores = new TreeMap<>());
|
||||
}
|
||||
if (!markParentAsDeleted && !filterMe) {
|
||||
FloatArrayList childScores = parentIdToChildScores.get(parent);
|
||||
if (childScores == null) {
|
||||
parentIdToChildScores.put(parent, childScores = new FloatArrayList());
|
||||
}
|
||||
childScores.add(1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete docs that are marked to be deleted.
|
||||
indexWriter.deleteDocuments(new Term("delete", "me"));
|
||||
indexWriter.commit();
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(directory);
|
||||
IndexSearcher searcher = new IndexSearcher(indexReader);
|
||||
Engine.Searcher engineSearcher = new Engine.Searcher(
|
||||
ChildrenQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
|
||||
int max = numUniqueChildValues / 4;
|
||||
for (int i = 0; i < max; i++) {
|
||||
// Simulate a parent update
|
||||
if (random().nextBoolean()) {
|
||||
final int numberOfUpdatableParents = numParentDocs - filteredOrDeletedDocs.size();
|
||||
int numberOfUpdates = RandomInts.randomIntBetween(random(), 0, Math.min(numberOfUpdatableParents, TEST_NIGHTLY ? 25 : 5));
|
||||
for (int j = 0; j < numberOfUpdates; j++) {
|
||||
int parentId;
|
||||
do {
|
||||
parentId = random().nextInt(numParentDocs);
|
||||
} while (filteredOrDeletedDocs.contains(parentId));
|
||||
|
||||
String parentUid = Uid.createUid("parent", Integer.toString(parentId));
|
||||
indexWriter.deleteDocuments(new Term(UidFieldMapper.NAME, parentUid));
|
||||
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, parentUid, Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
|
||||
indexReader.close();
|
||||
indexReader = DirectoryReader.open(indexWriter.w, true);
|
||||
searcher = new IndexSearcher(indexReader);
|
||||
engineSearcher = new Engine.Searcher(
|
||||
ChildrenConstantScoreQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
}
|
||||
|
||||
String childValue = childValues[random().nextInt(numUniqueChildValues)];
|
||||
int shortCircuitParentDocSet = random().nextInt(numParentDocs);
|
||||
ScoreType scoreType = ScoreType.values()[random().nextInt(ScoreType.values().length)];
|
||||
// leave min/max set to 0 half the time
|
||||
int minChildren = random().nextInt(2) * scaledRandomIntBetween(0, 110);
|
||||
int maxChildren = random().nextInt(2) * scaledRandomIntBetween(minChildren, 110);
|
||||
|
||||
QueryBuilder queryBuilder = hasChildQuery("child", constantScoreQuery(termQuery("field1", childValue)))
|
||||
.scoreType(scoreType.name().toLowerCase(Locale.ENGLISH))
|
||||
.minChildren(minChildren)
|
||||
.maxChildren(maxChildren)
|
||||
.setShortCircuitCutoff(shortCircuitParentDocSet);
|
||||
// Using a FQ, will invoke / test the Scorer#advance(..) and also let the Weight#scorer not get live docs as acceptedDocs
|
||||
queryBuilder = filteredQuery(queryBuilder, notQuery(termQuery("filter", "me")));
|
||||
Query query = parseQuery(queryBuilder);
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
int numHits = 1 + random().nextInt(25);
|
||||
TopScoreDocCollector actualTopDocsCollector = TopScoreDocCollector.create(numHits);
|
||||
searcher.search(query, MultiCollector.wrap(collector, actualTopDocsCollector));
|
||||
FixedBitSet actualResult = collector.getResult();
|
||||
|
||||
FixedBitSet expectedResult = new FixedBitSet(indexReader.maxDoc());
|
||||
TopScoreDocCollector expectedTopDocsCollector = TopScoreDocCollector.create(numHits);
|
||||
if (childValueToParentIds.containsKey(childValue)) {
|
||||
LeafReader slowLeafReader = SlowCompositeReaderWrapper.wrap(indexReader);
|
||||
final FloatArrayList[] scores = new FloatArrayList[slowLeafReader.maxDoc()];
|
||||
Terms terms = slowLeafReader.terms(UidFieldMapper.NAME);
|
||||
if (terms != null) {
|
||||
NavigableMap<String, FloatArrayList> parentIdToChildScores = childValueToParentIds.get(childValue);
|
||||
TermsEnum termsEnum = terms.iterator();
|
||||
PostingsEnum docsEnum = null;
|
||||
for (Map.Entry<String, FloatArrayList> entry : parentIdToChildScores.entrySet()) {
|
||||
int count = entry.getValue().elementsCount;
|
||||
if (count >= minChildren && (maxChildren == 0 || count <= maxChildren)) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("parent", entry.getKey()));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
final Bits liveDocs = slowLeafReader.getLiveDocs();
|
||||
for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
|
||||
if (liveDocs == null || liveDocs.get(doc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
expectedResult.set(docsEnum.docID());
|
||||
scores[docsEnum.docID()] = new FloatArrayList(entry.getValue());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MockScorer mockScorer = new MockScorer(scoreType);
|
||||
final LeafCollector leafCollector = expectedTopDocsCollector.getLeafCollector(slowLeafReader.getContext());
|
||||
leafCollector.setScorer(mockScorer);
|
||||
for (int doc = expectedResult.nextSetBit(0); doc < slowLeafReader.maxDoc(); doc = doc + 1 >= expectedResult.length() ? DocIdSetIterator.NO_MORE_DOCS : expectedResult.nextSetBit(doc + 1)) {
|
||||
mockScorer.scores = scores[doc];
|
||||
leafCollector.collect(doc);
|
||||
}
|
||||
}
|
||||
|
||||
assertBitSet(actualResult, expectedResult, searcher);
|
||||
assertTopDocs(actualTopDocsCollector.topDocs(), expectedTopDocsCollector.topDocs());
|
||||
}
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinScoreMode() throws IOException {
|
||||
assertScoreType(ScoreType.MIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxScoreMode() throws IOException {
|
||||
assertScoreType(ScoreType.MAX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAvgScoreMode() throws IOException {
|
||||
assertScoreType(ScoreType.AVG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSumScoreMode() throws IOException {
|
||||
assertScoreType(ScoreType.SUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the {@code scoreType} operates as expected and parents are found in the expected order.
|
||||
* <p />
|
||||
* This will use the test index's parent/child types to create parents with multiple children. Each child will have
|
||||
* a randomly generated scored stored in {@link #CHILD_SCORE_NAME}, which is used to score based on the
|
||||
* {@code scoreType} by using a {@link MockScorer} to determine the expected scores.
|
||||
* @param scoreType The score type to use within the query to score parents relative to their children.
|
||||
* @throws IOException if any unexpected error occurs
|
||||
*/
|
||||
private void assertScoreType(ScoreType scoreType) throws IOException {
|
||||
SearchContext context = SearchContext.current();
|
||||
Directory directory = newDirectory();
|
||||
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())));
|
||||
|
||||
// calculates the expected score per parent
|
||||
MockScorer scorer = new MockScorer(scoreType);
|
||||
scorer.scores = new FloatArrayList(10);
|
||||
|
||||
// number of parents to generate
|
||||
int parentDocs = scaledRandomIntBetween(2, 10);
|
||||
// unique child ID
|
||||
int childDocId = 0;
|
||||
|
||||
// Parent ID to expected score
|
||||
Map<String, Float> parentScores = new TreeMap<>();
|
||||
|
||||
// Add a few random parents to ensure that the children's score is appropriately taken into account
|
||||
for (int parentDocId = 0; parentDocId < parentDocs; ++parentDocId) {
|
||||
String parent = Integer.toString(parentDocId);
|
||||
|
||||
// Create the parent
|
||||
Document parentDocument = new Document();
|
||||
|
||||
parentDocument.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.YES));
|
||||
parentDocument.add(new StringField(IdFieldMapper.NAME, parent, Field.Store.YES));
|
||||
parentDocument.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
|
||||
// add the parent to the index
|
||||
writer.addDocument(parentDocument);
|
||||
|
||||
int numChildDocs = scaledRandomIntBetween(1, 10);
|
||||
|
||||
// forget any parent's previous scores
|
||||
scorer.scores.clear();
|
||||
|
||||
// associate children with the parent
|
||||
for (int i = 0; i < numChildDocs; ++i) {
|
||||
int childScore = random().nextInt(128);
|
||||
|
||||
Document childDocument = new Document();
|
||||
|
||||
childDocument.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", Integer.toString(childDocId++)), Field.Store.NO));
|
||||
childDocument.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
// parent association:
|
||||
childDocument.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
childDocument.add(new DoubleField(CHILD_SCORE_NAME, childScore, Field.Store.NO));
|
||||
|
||||
// remember the score to be calculated
|
||||
scorer.scores.add(childScore);
|
||||
|
||||
// add the associated child to the index
|
||||
writer.addDocument(childDocument);
|
||||
}
|
||||
|
||||
// this score that should be returned for this parent
|
||||
parentScores.put(parent, scorer.score());
|
||||
}
|
||||
|
||||
writer.commit();
|
||||
|
||||
IndexReader reader = DirectoryReader.open(writer, true);
|
||||
IndexSearcher searcher = new IndexSearcher(reader);
|
||||
|
||||
// setup to read the parent/child map
|
||||
Engine.Searcher engineSearcher = new Engine.Searcher(ChildrenQueryTests.class.getSimpleName(), searcher);
|
||||
((TestSearchContext)context).setSearcher(engineSearcher);
|
||||
|
||||
// child query that returns the score as the value of "childScore" for each child document, with the parent's score determined by the score type
|
||||
QueryBuilder childQueryBuilder = functionScoreQuery(typeQuery("child")).add(new FieldValueFactorFunctionBuilder(CHILD_SCORE_NAME));
|
||||
QueryBuilder queryBuilder = hasChildQuery("child", childQueryBuilder)
|
||||
.scoreType(scoreType.name().toLowerCase(Locale.ENGLISH))
|
||||
.setShortCircuitCutoff(parentDocs);
|
||||
|
||||
// Perform the search for the documents using the selected score type
|
||||
Query query = parseQuery(queryBuilder);
|
||||
TopDocs docs = searcher.search(query, parentDocs);
|
||||
assertThat("Expected all parents", docs.totalHits, is(parentDocs));
|
||||
|
||||
// score should be descending (just a sanity check)
|
||||
float topScore = docs.scoreDocs[0].score;
|
||||
|
||||
// ensure each score is returned as expected
|
||||
for (int i = 0; i < parentDocs; ++i) {
|
||||
ScoreDoc scoreDoc = docs.scoreDocs[i];
|
||||
// get the ID from the document to get its expected score; remove it so we cannot double-count it
|
||||
float score = parentScores.remove(reader.document(scoreDoc.doc).get(IdFieldMapper.NAME));
|
||||
|
||||
// expect exact match
|
||||
assertThat("Unexpected score", scoreDoc.score, is(score));
|
||||
assertThat("Not descending", score, lessThanOrEqualTo(topScore));
|
||||
|
||||
// it had better keep descending
|
||||
topScore = score;
|
||||
}
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
directory.close();
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import com.carrotsearch.hppc.FloatArrayList;
|
||||
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class MockScorer extends Scorer {
|
||||
|
||||
final ScoreType scoreType;
|
||||
FloatArrayList scores;
|
||||
|
||||
MockScorer(ScoreType scoreType) {
|
||||
super(null);
|
||||
this.scoreType = scoreType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
if (scoreType == ScoreType.NONE) {
|
||||
return 1.0f;
|
||||
}
|
||||
float aggregateScore = 0;
|
||||
|
||||
// in the case of a min value, it can't start at 0 (the lowest score); in all cases, it doesn't hurt to use the
|
||||
// first score, so we can safely use the first value by skipping it in the loop
|
||||
if (scores.elementsCount != 0) {
|
||||
aggregateScore = scores.buffer[0];
|
||||
|
||||
for (int i = 1; i < scores.elementsCount; i++) {
|
||||
float score = scores.buffer[i];
|
||||
switch (scoreType) {
|
||||
case MIN:
|
||||
if (aggregateScore > score) {
|
||||
aggregateScore = score;
|
||||
}
|
||||
break;
|
||||
case MAX:
|
||||
if (aggregateScore < score) {
|
||||
aggregateScore = score;
|
||||
}
|
||||
break;
|
||||
case SUM:
|
||||
case AVG:
|
||||
aggregateScore += score;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (scoreType == ScoreType.AVG) {
|
||||
aggregateScore /= scores.elementsCount;
|
||||
}
|
||||
}
|
||||
|
||||
return aggregateScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import com.carrotsearch.hppc.IntIntHashMap;
|
||||
import com.carrotsearch.hppc.ObjectObjectHashMap;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.index.engine.Engine;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ParentConstantScoreQueryTests extends AbstractChildTestCase {
|
||||
|
||||
@BeforeClass
|
||||
public static void before() throws IOException {
|
||||
SearchContext.setCurrent(createSearchContext("test", "parent", "child"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void after() throws IOException {
|
||||
SearchContext current = SearchContext.current();
|
||||
SearchContext.removeCurrent();
|
||||
Releasables.close(current);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicQuerySanities() {
|
||||
Query parentQuery = new TermQuery(new Term("field", "value"));
|
||||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper.fieldType());
|
||||
Filter childrenFilter = new QueryWrapperFilter(new TermQuery(new Term(TypeFieldMapper.NAME, "child")));
|
||||
Query query = new ParentConstantScoreQuery(parentChildIndexFieldData, parentQuery, "parent", childrenFilter);
|
||||
QueryUtils.check(query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
final Random r = random();
|
||||
final IndexWriterConfig iwc = LuceneTestCase.newIndexWriterConfig(r, new MockAnalyzer(r))
|
||||
.setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH)
|
||||
.setRAMBufferSizeMB(scaledRandomIntBetween(16, 64)); // we might index a lot - don't go crazy here
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(r, directory, iwc);
|
||||
int numUniqueParentValues = scaledRandomIntBetween(100, 2000);
|
||||
String[] parentValues = new String[numUniqueParentValues];
|
||||
for (int i = 0; i < numUniqueParentValues; i++) {
|
||||
parentValues[i] = Integer.toString(i);
|
||||
}
|
||||
|
||||
int childDocId = 0;
|
||||
int numParentDocs = scaledRandomIntBetween(1, numUniqueParentValues);
|
||||
ObjectObjectHashMap<String, NavigableSet<String>> parentValueToChildDocIds = new ObjectObjectHashMap<>();
|
||||
IntIntHashMap childIdToParentId = new IntIntHashMap();
|
||||
for (int parentDocId = 0; parentDocId < numParentDocs; parentDocId++) {
|
||||
boolean markParentAsDeleted = rarely();
|
||||
String parentValue = parentValues[random().nextInt(parentValues.length)];
|
||||
String parent = Integer.toString(parentDocId);
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
document.add(new StringField("field1", parentValue, Field.Store.NO));
|
||||
if (markParentAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
int numChildDocs = scaledRandomIntBetween(0, 100);
|
||||
if (parentDocId == numParentDocs - 1 && childIdToParentId.isEmpty()) {
|
||||
// ensure there is at least one child in the index
|
||||
numChildDocs = Math.max(1, numChildDocs);
|
||||
}
|
||||
for (int i = 0; i < numChildDocs; i++) {
|
||||
boolean markChildAsDeleted = rarely();
|
||||
boolean filterMe = rarely();
|
||||
String child = Integer.toString(childDocId++);
|
||||
|
||||
document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", child), Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
if (markChildAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
if (filterMe) {
|
||||
document.add(new StringField("filter", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
if (!markParentAsDeleted) {
|
||||
NavigableSet<String> childIds;
|
||||
if (parentValueToChildDocIds.containsKey(parentValue)) {
|
||||
childIds = parentValueToChildDocIds.get(parentValue);
|
||||
} else {
|
||||
parentValueToChildDocIds.put(parentValue, childIds = new TreeSet<>());
|
||||
}
|
||||
if (!markChildAsDeleted && !filterMe) {
|
||||
childIdToParentId.put(Integer.valueOf(child), parentDocId);
|
||||
childIds.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete docs that are marked to be deleted.
|
||||
indexWriter.deleteDocuments(new Term("delete", "me"));
|
||||
indexWriter.commit();
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(directory);
|
||||
IndexSearcher searcher = new IndexSearcher(indexReader);
|
||||
Engine.Searcher engineSearcher = new Engine.Searcher(
|
||||
ParentConstantScoreQuery.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
|
||||
int max = numUniqueParentValues / 4;
|
||||
for (int i = 0; i < max; i++) {
|
||||
// Simulate a child update
|
||||
if (random().nextBoolean()) {
|
||||
int numberOfUpdates = childIdToParentId.isEmpty() ? 0 : scaledRandomIntBetween(1, 25);
|
||||
int[] childIds = childIdToParentId.keys().toArray();
|
||||
for (int j = 0; j < numberOfUpdates; j++) {
|
||||
int childId = childIds[random().nextInt(childIds.length)];
|
||||
String childUid = Uid.createUid("child", Integer.toString(childId));
|
||||
indexWriter.deleteDocuments(new Term(UidFieldMapper.NAME, childUid));
|
||||
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, childUid, Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
String parentUid = Uid.createUid("parent", Integer.toString(childIdToParentId.get(childId)));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, parentUid, Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
|
||||
indexReader.close();
|
||||
indexReader = DirectoryReader.open(indexWriter.w, true);
|
||||
searcher = new IndexSearcher(indexReader);
|
||||
engineSearcher = new Engine.Searcher(
|
||||
ParentConstantScoreQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
}
|
||||
|
||||
String parentValue = parentValues[random().nextInt(numUniqueParentValues)];
|
||||
QueryBuilder queryBuilder;
|
||||
if (random().nextBoolean()) {
|
||||
queryBuilder = hasParentQuery("parent", termQuery("field1", parentValue));
|
||||
} else {
|
||||
queryBuilder = constantScoreQuery(hasParentQuery("parent", termQuery("field1", parentValue)));
|
||||
}
|
||||
// Using a FQ, will invoke / test the Scorer#advance(..) and also let the Weight#scorer not get live docs as acceptedDocs
|
||||
queryBuilder = filteredQuery(queryBuilder, notQuery(termQuery("filter", "me")));
|
||||
Query query = parseQuery(queryBuilder);
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
searcher.search(query, collector);
|
||||
FixedBitSet actualResult = collector.getResult();
|
||||
|
||||
FixedBitSet expectedResult = new FixedBitSet(indexReader.maxDoc());
|
||||
if (parentValueToChildDocIds.containsKey(parentValue)) {
|
||||
LeafReader slowLeafReader = SlowCompositeReaderWrapper.wrap(indexReader);
|
||||
Terms terms = slowLeafReader.terms(UidFieldMapper.NAME);
|
||||
if (terms != null) {
|
||||
NavigableSet<String> childIds = parentValueToChildDocIds.get(parentValue);
|
||||
TermsEnum termsEnum = terms.iterator();
|
||||
PostingsEnum docsEnum = null;
|
||||
for (String id : childIds) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("child", id));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
final Bits liveDocs = slowLeafReader.getLiveDocs();
|
||||
for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
|
||||
if (liveDocs == null || liveDocs.get(doc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
expectedResult.set(docsEnum.docID());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertBitSet(actualResult, expectedResult, searcher);
|
||||
}
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import com.carrotsearch.hppc.FloatArrayList;
|
||||
import com.carrotsearch.hppc.IntIntHashMap;
|
||||
import com.carrotsearch.hppc.ObjectObjectHashMap;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.index.engine.Engine;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
|
||||
public class ParentQueryTests extends AbstractChildTestCase {
|
||||
|
||||
@BeforeClass
|
||||
public static void before() throws IOException {
|
||||
SearchContext.setCurrent(createSearchContext("test", "parent", "child"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void after() throws IOException {
|
||||
SearchContext current = SearchContext.current();
|
||||
SearchContext.removeCurrent();
|
||||
Releasables.close(current);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicQuerySanities() {
|
||||
Query parentQuery = new TermQuery(new Term("field", "value"));
|
||||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper.fieldType());
|
||||
Filter childrenFilter = new QueryWrapperFilter(new TermQuery(new Term(TypeFieldMapper.NAME, "child")));
|
||||
Query query = new ParentQuery(parentChildIndexFieldData, parentQuery, "parent", childrenFilter);
|
||||
QueryUtils.check(query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom() throws Exception {
|
||||
Directory directory = newDirectory();
|
||||
final Random r = random();
|
||||
final IndexWriterConfig iwc = LuceneTestCase.newIndexWriterConfig(r, new MockAnalyzer(r))
|
||||
.setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH)
|
||||
.setRAMBufferSizeMB(scaledRandomIntBetween(16, 64)); // we might index a lot - don't go crazy here
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(r, directory, iwc);
|
||||
int numUniqueParentValues = scaledRandomIntBetween(100, 2000);
|
||||
String[] parentValues = new String[numUniqueParentValues];
|
||||
for (int i = 0; i < numUniqueParentValues; i++) {
|
||||
parentValues[i] = Integer.toString(i);
|
||||
}
|
||||
|
||||
int childDocId = 0;
|
||||
int numParentDocs = scaledRandomIntBetween(1, numUniqueParentValues);
|
||||
ObjectObjectHashMap<String, NavigableMap<String, Float>> parentValueToChildIds = new ObjectObjectHashMap<>();
|
||||
IntIntHashMap childIdToParentId = new IntIntHashMap();
|
||||
for (int parentDocId = 0; parentDocId < numParentDocs; parentDocId++) {
|
||||
boolean markParentAsDeleted = rarely();
|
||||
String parentValue = parentValues[random().nextInt(parentValues.length)];
|
||||
String parent = Integer.toString(parentDocId);
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO));
|
||||
document.add(new StringField("field1", parentValue, Field.Store.NO));
|
||||
if (markParentAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
int numChildDocs = scaledRandomIntBetween(0, 100);
|
||||
if (parentDocId == numParentDocs - 1 && childIdToParentId.isEmpty()) {
|
||||
// ensure there is at least one child in the index
|
||||
numChildDocs = Math.max(1, numChildDocs);
|
||||
}
|
||||
for (int i = 0; i < numChildDocs; i++) {
|
||||
String child = Integer.toString(childDocId++);
|
||||
boolean markChildAsDeleted = rarely();
|
||||
boolean filterMe = rarely();
|
||||
document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", child), Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO));
|
||||
if (markChildAsDeleted) {
|
||||
document.add(new StringField("delete", "me", Field.Store.NO));
|
||||
}
|
||||
if (filterMe) {
|
||||
document.add(new StringField("filter", "me", Field.Store.NO));
|
||||
}
|
||||
indexWriter.addDocument(document);
|
||||
|
||||
if (!markParentAsDeleted) {
|
||||
NavigableMap<String, Float> childIdToScore = parentValueToChildIds.getOrDefault(parentValue, null);
|
||||
if (childIdToScore == null) {
|
||||
parentValueToChildIds.put(parentValue, childIdToScore = new TreeMap<>());
|
||||
}
|
||||
if (!markChildAsDeleted && !filterMe) {
|
||||
assertFalse("child ["+ child + "] already has a score", childIdToScore.containsKey(child));
|
||||
childIdToScore.put(child, 1f);
|
||||
childIdToParentId.put(Integer.valueOf(child), parentDocId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete docs that are marked to be deleted.
|
||||
indexWriter.deleteDocuments(new Term("delete", "me"));
|
||||
indexWriter.commit();
|
||||
|
||||
IndexReader indexReader = DirectoryReader.open(directory);
|
||||
IndexSearcher searcher = new IndexSearcher(indexReader);
|
||||
Engine.Searcher engineSearcher = new Engine.Searcher(
|
||||
ParentQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
|
||||
int max = numUniqueParentValues / 4;
|
||||
for (int i = 0; i < max; i++) {
|
||||
// Simulate a child update
|
||||
if (random().nextBoolean()) {
|
||||
int numberOfUpdates = childIdToParentId.isEmpty() ? 0 : scaledRandomIntBetween(1, 5);
|
||||
int[] childIds = childIdToParentId.keys().toArray();
|
||||
for (int j = 0; j < numberOfUpdates; j++) {
|
||||
int childId = childIds[random().nextInt(childIds.length)];
|
||||
String childUid = Uid.createUid("child", Integer.toString(childId));
|
||||
indexWriter.deleteDocuments(new Term(UidFieldMapper.NAME, childUid));
|
||||
|
||||
Document document = new Document();
|
||||
document.add(new StringField(UidFieldMapper.NAME, childUid, Field.Store.YES));
|
||||
document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO));
|
||||
String parentUid = Uid.createUid("parent", Integer.toString(childIdToParentId.get(childId)));
|
||||
document.add(new StringField(ParentFieldMapper.NAME, parentUid, Field.Store.NO));
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
|
||||
indexReader.close();
|
||||
indexReader = DirectoryReader.open(indexWriter.w, true);
|
||||
searcher = new IndexSearcher(indexReader);
|
||||
engineSearcher = new Engine.Searcher(
|
||||
ParentConstantScoreQueryTests.class.getSimpleName(), searcher
|
||||
);
|
||||
((TestSearchContext) SearchContext.current()).setSearcher(engineSearcher);
|
||||
}
|
||||
|
||||
String parentValue = parentValues[random().nextInt(numUniqueParentValues)];
|
||||
QueryBuilder queryBuilder = hasParentQuery("parent", constantScoreQuery(termQuery("field1", parentValue)));
|
||||
// Using a FQ, will invoke / test the Scorer#advance(..) and also let the Weight#scorer not get live docs as acceptedDocs
|
||||
queryBuilder = filteredQuery(queryBuilder, notQuery(termQuery("filter", "me")));
|
||||
Query query = parseQuery(queryBuilder);
|
||||
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
int numHits = 1 + random().nextInt(25);
|
||||
TopScoreDocCollector actualTopDocsCollector = TopScoreDocCollector.create(numHits);
|
||||
searcher.search(query, MultiCollector.wrap(collector, actualTopDocsCollector));
|
||||
FixedBitSet actualResult = collector.getResult();
|
||||
|
||||
FixedBitSet expectedResult = new FixedBitSet(indexReader.maxDoc());
|
||||
TopScoreDocCollector expectedTopDocsCollector = TopScoreDocCollector.create(numHits);
|
||||
if (parentValueToChildIds.containsKey(parentValue)) {
|
||||
LeafReader slowLeafReader = SlowCompositeReaderWrapper.wrap(indexReader);
|
||||
final FloatArrayList[] scores = new FloatArrayList[slowLeafReader.maxDoc()];
|
||||
Terms terms = slowLeafReader.terms(UidFieldMapper.NAME);
|
||||
if (terms != null) {
|
||||
NavigableMap<String, Float> childIdsAndScore = parentValueToChildIds.get(parentValue);
|
||||
TermsEnum termsEnum = terms.iterator();
|
||||
PostingsEnum docsEnum = null;
|
||||
for (Map.Entry<String, Float> entry : childIdsAndScore.entrySet()) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("child", entry.getKey()));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.postings(docsEnum, PostingsEnum.NONE);
|
||||
final Bits liveDocs = slowLeafReader.getLiveDocs();
|
||||
for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
|
||||
if (liveDocs == null || liveDocs.get(doc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
expectedResult.set(docsEnum.docID());
|
||||
FloatArrayList s = scores[docsEnum.docID()];
|
||||
if (s == null) {
|
||||
scores[docsEnum.docID()] = s = new FloatArrayList(2);
|
||||
}
|
||||
s.add(entry.getValue());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
MockScorer mockScorer = new MockScorer(ScoreType.MAX);
|
||||
mockScorer.scores = new FloatArrayList();
|
||||
final LeafCollector leafCollector = expectedTopDocsCollector.getLeafCollector(slowLeafReader.getContext());
|
||||
leafCollector.setScorer(mockScorer);
|
||||
for (int doc = expectedResult.nextSetBit(0); doc < slowLeafReader.maxDoc(); doc = doc + 1 >= expectedResult.length() ? DocIdSetIterator.NO_MORE_DOCS : expectedResult.nextSetBit(doc + 1)) {
|
||||
mockScorer.scores.clear();
|
||||
mockScorer.scores.addAll(scores[doc]);
|
||||
leafCollector.collect(doc);
|
||||
}
|
||||
}
|
||||
|
||||
assertBitSet(actualResult, expectedResult, searcher);
|
||||
assertTopDocs(actualTopDocsCollector.topDocs(), expectedTopDocsCollector.topDocs());
|
||||
}
|
||||
|
||||
indexWriter.close();
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse;
|
||||
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
|
||||
import org.elasticsearch.action.explain.ExplainResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.fielddata.FieldDataType;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.test.ESIntegTestCase.Scope;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.hasParentQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllSuccessful;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ClusterScope(scope = Scope.SUITE)
|
||||
public class ChildQuerySearchBwcIT extends ChildQuerySearchIT {
|
||||
|
||||
@Override
|
||||
public Settings indexSettings() {
|
||||
return settings(Version.V_1_6_0).put(super.indexSettings()).build();
|
||||
}
|
||||
|
||||
public void testSelfReferentialIsForbidden() {
|
||||
// we allowed this, but it was actually broken. The has_child/has_parent results were sometimes wrong...
|
||||
assertAcked(prepareCreate("test").addMapping("type", "_parent", "type=type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd_ParentFieldAfterIndexingParentDocButBeforeIndexingChildDoc() throws Exception {
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(Settings.builder()
|
||||
.put(indexSettings())
|
||||
.put("index.refresh_interval", -1)));
|
||||
ensureGreen();
|
||||
|
||||
String parentId = "p1";
|
||||
client().prepareIndex("test", "parent", parentId).setSource("p_field", "1").get();
|
||||
refresh();
|
||||
assertAcked(client().admin()
|
||||
.indices()
|
||||
.preparePutMapping("test")
|
||||
.setType("child")
|
||||
.setSource("_parent", "type=parent"));
|
||||
client().prepareIndex("test", "child", "c1").setSource("c_field", "1").setParent(parentId).get();
|
||||
client().admin().indices().prepareRefresh().get();
|
||||
|
||||
SearchResponse searchResponse = client().prepareSearch("test")
|
||||
.setQuery(hasChildQuery("child", termQuery("c_field", "1")))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertSearchHits(searchResponse, parentId);
|
||||
|
||||
searchResponse = client().prepareSearch("test")
|
||||
.setQuery(hasChildQuery("child", termQuery("c_field", "1")).scoreType("max"))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertSearchHits(searchResponse, parentId);
|
||||
|
||||
|
||||
searchResponse = client().prepareSearch("test")
|
||||
.setPostFilter(hasChildQuery("child", termQuery("c_field", "1")))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertSearchHits(searchResponse, parentId);
|
||||
|
||||
searchResponse = client().prepareSearch("test")
|
||||
.setPostFilter(hasParentQuery("parent", termQuery("p_field", "1")))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertSearchHits(searchResponse, "c1");
|
||||
|
||||
searchResponse = client().prepareSearch("test")
|
||||
.setQuery(hasParentQuery("parent", termQuery("p_field", "1")).scoreType("score"))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertSearchHits(searchResponse, "c1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplainUsage() throws Exception {
|
||||
assertAcked(prepareCreate("test")
|
||||
.addMapping("parent")
|
||||
.addMapping("child", "_parent", "type=parent"));
|
||||
ensureGreen();
|
||||
|
||||
String parentId = "p1";
|
||||
client().prepareIndex("test", "parent", parentId).setSource("p_field", "1").get();
|
||||
client().prepareIndex("test", "child", "c1").setSource("c_field", "1").setParent(parentId).get();
|
||||
refresh();
|
||||
|
||||
SearchResponse searchResponse = client().prepareSearch("test")
|
||||
.setExplain(true)
|
||||
.setQuery(hasChildQuery("child", termQuery("c_field", "1")).scoreType("max"))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("not implemented yet..."));
|
||||
|
||||
searchResponse = client().prepareSearch("test")
|
||||
.setExplain(true)
|
||||
.setQuery(hasParentQuery("parent", termQuery("p_field", "1")).scoreType("score"))
|
||||
.get();
|
||||
assertHitCount(searchResponse, 1l);
|
||||
assertThat(searchResponse.getHits().getAt(0).explanation().getDescription(), equalTo("not implemented yet..."));
|
||||
|
||||
ExplainResponse explainResponse = client().prepareExplain("test", "parent", parentId)
|
||||
.setQuery(hasChildQuery("child", termQuery("c_field", "1")).scoreType("max"))
|
||||
.get();
|
||||
assertThat(explainResponse.isExists(), equalTo(true));
|
||||
// TODO: improve test once explanations are actually implemented
|
||||
assertThat(explainResponse.getExplanation().toString(), startsWith("1.0 ="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentFieldDataCacheBug() throws Exception {
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(Settings.builder().put(indexSettings())
|
||||
.put("index.refresh_interval", -1)) // Disable automatic refresh, so that the _parent doesn't get warmed
|
||||
.addMapping("parent", jsonBuilder().startObject().startObject("parent")
|
||||
.startObject("properties")
|
||||
.startObject("p_field")
|
||||
.field("type", "string")
|
||||
.startObject("fielddata")
|
||||
.field(FieldDataType.FORMAT_KEY, MappedFieldType.Loading.LAZY)
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject().endObject()));
|
||||
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "p0").setSource("p_field", "p_value0").get();
|
||||
client().prepareIndex("test", "parent", "p1").setSource("p_field", "p_value1").get();
|
||||
|
||||
refresh();
|
||||
// No _parent field yet, there shouldn't be anything in the field data for _parent field
|
||||
IndicesStatsResponse indicesStatsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).get();
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getMemorySizeInBytes(), equalTo(0l));
|
||||
|
||||
// Now add mapping + children
|
||||
client().admin().indices().preparePutMapping("test").setType("child")
|
||||
.setSource(XContentFactory.jsonBuilder().startObject().startObject("child")
|
||||
.startObject("_parent")
|
||||
.field("type", "parent")
|
||||
.endObject()
|
||||
.startObject("properties")
|
||||
.startObject("c_field")
|
||||
.field("type", "string")
|
||||
.startObject("fielddata")
|
||||
.field(FieldDataType.FORMAT_KEY, MappedFieldType.Loading.LAZY)
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject().endObject())
|
||||
.get();
|
||||
|
||||
// index simple data
|
||||
client().prepareIndex("test", "child", "c1").setSource("c_field", "red").setParent("p1").get();
|
||||
client().prepareIndex("test", "child", "c2").setSource("c_field", "yellow").setParent("p1").get();
|
||||
client().prepareIndex("test", "parent", "p2").setSource("p_field", "p_value2").get();
|
||||
client().prepareIndex("test", "child", "c3").setSource("c_field", "blue").setParent("p2").get();
|
||||
client().prepareIndex("test", "child", "c4").setSource("c_field", "red").setParent("p2").get();
|
||||
|
||||
refresh();
|
||||
|
||||
indicesStatsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getMemorySizeInBytes(), greaterThan(0l));
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getFields().get("_parent"), greaterThan(0l));
|
||||
|
||||
SearchResponse searchResponse = client().prepareSearch("test")
|
||||
.setQuery(constantScoreQuery(hasChildQuery("child", termQuery("c_field", "blue"))))
|
||||
.get();
|
||||
assertNoFailures(searchResponse);
|
||||
assertThat(searchResponse.getHits().totalHits(), equalTo(1l));
|
||||
|
||||
indicesStatsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getMemorySizeInBytes(), greaterThan(0l));
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getFields().get("_parent"), greaterThan(0l));
|
||||
|
||||
ClearIndicesCacheResponse clearCacheResponse = client().admin().indices().prepareClearCache("test").setFieldDataCache(true).get();
|
||||
assertNoFailures(clearCacheResponse);
|
||||
assertAllSuccessful(clearCacheResponse);
|
||||
indicesStatsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getMemorySizeInBytes(), equalTo(0l));
|
||||
assertThat(indicesStatsResponse.getTotal().getFieldData().getFields().get("_parent"), equalTo(0l));
|
||||
}
|
||||
}
|
|
@ -30,12 +30,11 @@ import org.elasticsearch.common.lucene.search.function.CombineFunction;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.index.cache.IndexCacheModule;
|
||||
import org.elasticsearch.index.cache.query.index.IndexQueryCache;
|
||||
import org.elasticsearch.index.mapper.MergeMappingException;
|
||||
import org.elasticsearch.index.query.HasChildQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.search.child.ScoreType;
|
||||
import org.elasticsearch.index.query.ScoreType;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
|
@ -1573,7 +1572,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
return indexBuilders;
|
||||
}
|
||||
|
||||
private SearchResponse minMaxQuery(String scoreType, int minChildren, int maxChildren, int cutoff) throws SearchPhaseExecutionException {
|
||||
private SearchResponse minMaxQuery(String scoreType, int minChildren, int maxChildren) throws SearchPhaseExecutionException {
|
||||
return client()
|
||||
.prepareSearch("test")
|
||||
.setQuery(
|
||||
|
@ -1584,16 +1583,16 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
.add(QueryBuilders.matchAllQuery(), weightFactorFunction(1))
|
||||
.add(QueryBuilders.termQuery("foo", "three"), weightFactorFunction(1))
|
||||
.add(QueryBuilders.termQuery("foo", "four"), weightFactorFunction(1))).scoreType(scoreType)
|
||||
.minChildren(minChildren).maxChildren(maxChildren).setShortCircuitCutoff(cutoff))
|
||||
.minChildren(minChildren).maxChildren(maxChildren))
|
||||
.addSort("_score", SortOrder.DESC).addSort("id", SortOrder.ASC).get();
|
||||
}
|
||||
|
||||
private SearchResponse minMaxFilter(int minChildren, int maxChildren, int cutoff) throws SearchPhaseExecutionException {
|
||||
private SearchResponse minMaxFilter(int minChildren, int maxChildren) throws SearchPhaseExecutionException {
|
||||
return client()
|
||||
.prepareSearch("test")
|
||||
.setQuery(
|
||||
QueryBuilders.constantScoreQuery(QueryBuilders.hasChildQuery("child", termQuery("foo", "two"))
|
||||
.minChildren(minChildren).maxChildren(maxChildren).setShortCircuitCutoff(cutoff)))
|
||||
.minChildren(minChildren).maxChildren(maxChildren)))
|
||||
.addSort("id", SortOrder.ASC).setTrackScores(true).get();
|
||||
}
|
||||
|
||||
|
@ -1606,10 +1605,9 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
|
||||
indexRandom(true, createMinMaxDocBuilders().toArray(new IndexRequestBuilder[0]));
|
||||
SearchResponse response;
|
||||
int cutoff = getRandom().nextInt(4);
|
||||
|
||||
// Score mode = NONE
|
||||
response = minMaxQuery("none", 0, 0, cutoff);
|
||||
response = minMaxQuery("none", 0, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1619,7 +1617,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 1, 0, cutoff);
|
||||
response = minMaxQuery("none", 1, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1629,7 +1627,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 2, 0, cutoff);
|
||||
response = minMaxQuery("none", 2, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
|
@ -1637,17 +1635,17 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 3, 0, cutoff);
|
||||
response = minMaxQuery("none", 3, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 4, 0, cutoff);
|
||||
response = minMaxQuery("none", 4, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = minMaxQuery("none", 0, 4, cutoff);
|
||||
response = minMaxQuery("none", 0, 4);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1657,7 +1655,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 0, 3, cutoff);
|
||||
response = minMaxQuery("none", 0, 3);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1667,7 +1665,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 0, 2, cutoff);
|
||||
response = minMaxQuery("none", 0, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1675,21 +1673,21 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("none", 2, 2, cutoff);
|
||||
response = minMaxQuery("none", 2, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
try {
|
||||
response = minMaxQuery("none", 3, 2, cutoff);
|
||||
response = minMaxQuery("none", 3, 2);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = SUM
|
||||
response = minMaxQuery("sum", 0, 0, cutoff);
|
||||
response = minMaxQuery("sum", 0, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1699,7 +1697,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("sum", 1, 0, cutoff);
|
||||
response = minMaxQuery("sum", 1, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1709,7 +1707,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("sum", 2, 0, cutoff);
|
||||
response = minMaxQuery("sum", 2, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1717,17 +1715,17 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
|
||||
response = minMaxQuery("sum", 3, 0, cutoff);
|
||||
response = minMaxQuery("sum", 3, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
|
||||
response = minMaxQuery("sum", 4, 0, cutoff);
|
||||
response = minMaxQuery("sum", 4, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = minMaxQuery("sum", 0, 4, cutoff);
|
||||
response = minMaxQuery("sum", 0, 4);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1737,7 +1735,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("sum", 0, 3, cutoff);
|
||||
response = minMaxQuery("sum", 0, 3);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1747,7 +1745,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("sum", 0, 2, cutoff);
|
||||
response = minMaxQuery("sum", 0, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
|
@ -1755,21 +1753,21 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("sum", 2, 2, cutoff);
|
||||
response = minMaxQuery("sum", 2, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
|
||||
try {
|
||||
response = minMaxQuery("sum", 3, 2, cutoff);
|
||||
response = minMaxQuery("sum", 3, 2);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = MAX
|
||||
response = minMaxQuery("max", 0, 0, cutoff);
|
||||
response = minMaxQuery("max", 0, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1779,7 +1777,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("max", 1, 0, cutoff);
|
||||
response = minMaxQuery("max", 1, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1789,7 +1787,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("max", 2, 0, cutoff);
|
||||
response = minMaxQuery("max", 2, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1797,17 +1795,17 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
|
||||
response = minMaxQuery("max", 3, 0, cutoff);
|
||||
response = minMaxQuery("max", 3, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
|
||||
response = minMaxQuery("max", 4, 0, cutoff);
|
||||
response = minMaxQuery("max", 4, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = minMaxQuery("max", 0, 4, cutoff);
|
||||
response = minMaxQuery("max", 0, 4);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1817,7 +1815,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("max", 0, 3, cutoff);
|
||||
response = minMaxQuery("max", 0, 3);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1827,7 +1825,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("max", 0, 2, cutoff);
|
||||
response = minMaxQuery("max", 0, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
|
@ -1835,21 +1833,21 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("max", 2, 2, cutoff);
|
||||
response = minMaxQuery("max", 2, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
|
||||
try {
|
||||
response = minMaxQuery("max", 3, 2, cutoff);
|
||||
response = minMaxQuery("max", 3, 2);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = AVG
|
||||
response = minMaxQuery("avg", 0, 0, cutoff);
|
||||
response = minMaxQuery("avg", 0, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1859,7 +1857,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("avg", 1, 0, cutoff);
|
||||
response = minMaxQuery("avg", 1, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1869,7 +1867,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("avg", 2, 0, cutoff);
|
||||
response = minMaxQuery("avg", 2, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1877,17 +1875,17 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
|
||||
response = minMaxQuery("avg", 3, 0, cutoff);
|
||||
response = minMaxQuery("avg", 3, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
|
||||
response = minMaxQuery("avg", 4, 0, cutoff);
|
||||
response = minMaxQuery("avg", 4, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = minMaxQuery("avg", 0, 4, cutoff);
|
||||
response = minMaxQuery("avg", 0, 4);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1897,7 +1895,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("avg", 0, 3, cutoff);
|
||||
response = minMaxQuery("avg", 0, 3);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
|
@ -1907,7 +1905,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("avg", 0, 2, cutoff);
|
||||
response = minMaxQuery("avg", 0, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
|
@ -1915,21 +1913,21 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxQuery("avg", 2, 2, cutoff);
|
||||
response = minMaxQuery("avg", 2, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1.5f));
|
||||
|
||||
try {
|
||||
response = minMaxQuery("avg", 3, 2, cutoff);
|
||||
response = minMaxQuery("avg", 3, 2);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// HasChildFilter
|
||||
response = minMaxFilter(0, 0, cutoff);
|
||||
response = minMaxFilter(0, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1939,7 +1937,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(1, 0, cutoff);
|
||||
response = minMaxFilter(1, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1949,7 +1947,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(2, 0, cutoff);
|
||||
response = minMaxFilter(2, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
|
@ -1957,17 +1955,17 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(3, 0, cutoff);
|
||||
response = minMaxFilter(3, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(4, 0, cutoff);
|
||||
response = minMaxFilter(4, 0);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = minMaxFilter(0, 4, cutoff);
|
||||
response = minMaxFilter(0, 4);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1977,7 +1975,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(0, 3, cutoff);
|
||||
response = minMaxFilter(0, 3);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1987,7 +1985,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(0, 2, cutoff);
|
||||
response = minMaxFilter(0, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
|
@ -1995,14 +1993,14 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = minMaxFilter(2, 2, cutoff);
|
||||
response = minMaxFilter(2, 2);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
try {
|
||||
response = minMaxFilter(3, 2, cutoff);
|
||||
response = minMaxFilter(3, 2);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.toString(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
|
@ -2033,7 +2031,6 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
|
|||
|
||||
static HasChildQueryBuilder hasChildQuery(String type, QueryBuilder queryBuilder) {
|
||||
HasChildQueryBuilder hasChildQueryBuilder = QueryBuilders.hasChildQuery(type, queryBuilder);
|
||||
hasChildQueryBuilder.setShortCircuitCutoff(randomInt(10));
|
||||
return hasChildQueryBuilder;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
* 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.search.child;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
|
||||
import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
|
||||
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.IndexService;
|
||||
import org.elasticsearch.index.fielddata.FieldDataType;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.shard.IndexShard;
|
||||
import org.elasticsearch.index.shard.MergePolicyConfig;
|
||||
import org.elasticsearch.indices.IndicesService;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
|
||||
import static org.elasticsearch.search.child.ChildQuerySearchIT.hasChildQuery;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
|
||||
public class ParentFieldLoadingBwcIT extends ESIntegTestCase {
|
||||
|
||||
private final Settings indexSettings = Settings.builder()
|
||||
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
|
||||
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
|
||||
.put(IndexShard.INDEX_REFRESH_INTERVAL, -1)
|
||||
// We never want merges in this test to ensure we have two segments for the last validation
|
||||
.put(MergePolicyConfig.INDEX_MERGE_ENABLED, false)
|
||||
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_6_0)
|
||||
.build();
|
||||
|
||||
public void testParentFieldDataCacheBug() throws Exception {
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(Settings.builder().put(indexSettings)
|
||||
.put("index.refresh_interval", -1)) // Disable automatic refresh, so that the _parent doesn't get warmed
|
||||
.addMapping("parent", XContentFactory.jsonBuilder().startObject().startObject("parent")
|
||||
.startObject("properties")
|
||||
.startObject("p_field")
|
||||
.field("type", "string")
|
||||
.startObject("fielddata")
|
||||
.field(FieldDataType.FORMAT_KEY, MappedFieldType.Loading.LAZY)
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject().endObject())
|
||||
.addMapping("child", XContentFactory.jsonBuilder().startObject().startObject("child")
|
||||
.startObject("_parent")
|
||||
.field("type", "parent")
|
||||
.endObject()
|
||||
.startObject("properties")
|
||||
.startObject("c_field")
|
||||
.field("type", "string")
|
||||
.startObject("fielddata")
|
||||
.field(FieldDataType.FORMAT_KEY, MappedFieldType.Loading.LAZY)
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject().endObject()));
|
||||
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "p0").setSource("p_field", "p_value0").get();
|
||||
client().prepareIndex("test", "parent", "p1").setSource("p_field", "p_value1").get();
|
||||
client().prepareIndex("test", "child", "c1").setSource("c_field", "red").setParent("p1").get();
|
||||
client().prepareIndex("test", "child", "c2").setSource("c_field", "yellow").setParent("p1").get();
|
||||
client().prepareIndex("test", "parent", "p2").setSource("p_field", "p_value2").get();
|
||||
client().prepareIndex("test", "child", "c3").setSource("c_field", "blue").setParent("p2").get();
|
||||
client().prepareIndex("test", "child", "c4").setSource("c_field", "red").setParent("p2").get();
|
||||
refresh();
|
||||
|
||||
IndicesStatsResponse statsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(statsResponse.getTotal().getFieldData().getMemorySizeInBytes(), greaterThan(0l));
|
||||
assertThat(statsResponse.getTotal().getFieldData().getFields().get("_parent"), greaterThan(0l));
|
||||
|
||||
SearchResponse searchResponse = client().prepareSearch("test")
|
||||
.setQuery(constantScoreQuery(hasChildQuery("child", termQuery("c_field", "blue"))))
|
||||
.get();
|
||||
assertNoFailures(searchResponse);
|
||||
assertThat(searchResponse.getHits().totalHits(), equalTo(1l));
|
||||
|
||||
statsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(statsResponse.getTotal().getFieldData().getMemorySizeInBytes(), greaterThan(0l));
|
||||
assertThat(statsResponse.getTotal().getFieldData().getFields().get("_parent"), greaterThan(0l));
|
||||
|
||||
ClearIndicesCacheResponse clearCacheResponse = client().admin().indices().prepareClearCache("test").setFieldDataCache(true).get();
|
||||
assertNoFailures(clearCacheResponse);
|
||||
assertAllSuccessful(clearCacheResponse);
|
||||
statsResponse = client().admin().indices()
|
||||
.prepareStats("test").setFieldData(true).setFieldDataFields("_parent").get();
|
||||
assertThat(statsResponse.getTotal().getFieldData().getMemorySizeInBytes(), equalTo(0l));
|
||||
assertThat(statsResponse.getTotal().getFieldData().getFields().get("_parent"), equalTo(0l));
|
||||
}
|
||||
|
||||
public void testEagerParentFieldLoading() throws Exception {
|
||||
logger.info("testing lazy loading...");
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(indexSettings)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", childMapping(MappedFieldType.Loading.LAZY)));
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "1").setSource("{}").get();
|
||||
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
IndicesStatsResponse r = client().admin().indices().prepareStats("test").setFieldData(true).setFieldDataFields("*").get();
|
||||
ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get();
|
||||
assertThat(response.getIndicesStats().getFieldData().getMemorySizeInBytes(), equalTo(0l));
|
||||
|
||||
logger.info("testing default loading...");
|
||||
assertAcked(client().admin().indices().prepareDelete("test").get());
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(indexSettings)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", "_parent", "type=parent"));
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "1").setSource("{}").get();
|
||||
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
response = client().admin().cluster().prepareClusterStats().get();
|
||||
long fielddataSizeDefault = response.getIndicesStats().getFieldData().getMemorySizeInBytes();
|
||||
assertThat(fielddataSizeDefault, greaterThan(0l));
|
||||
|
||||
logger.info("testing eager loading...");
|
||||
assertAcked(client().admin().indices().prepareDelete("test").get());
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(indexSettings)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", childMapping(MappedFieldType.Loading.EAGER)));
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "1").setSource("{}").get();
|
||||
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
response = client().admin().cluster().prepareClusterStats().get();
|
||||
assertThat(response.getIndicesStats().getFieldData().getMemorySizeInBytes(), equalTo(fielddataSizeDefault));
|
||||
|
||||
logger.info("testing eager global ordinals loading...");
|
||||
assertAcked(client().admin().indices().prepareDelete("test").get());
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(indexSettings)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", childMapping(MappedFieldType.Loading.EAGER_GLOBAL_ORDINALS)));
|
||||
ensureGreen();
|
||||
|
||||
// Need to do 2 separate refreshes, otherwise we have 1 segment and then we can't measure if global ordinals
|
||||
// is loaded by the size of the field data cache, because global ordinals on 1 segment shards takes no extra memory.
|
||||
client().prepareIndex("test", "parent", "1").setSource("{}").get();
|
||||
refresh();
|
||||
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
response = client().admin().cluster().prepareClusterStats().get();
|
||||
assertThat(response.getIndicesStats().getFieldData().getMemorySizeInBytes(), greaterThan(fielddataSizeDefault));
|
||||
}
|
||||
|
||||
public void testChangingEagerParentFieldLoadingAtRuntime() throws Exception {
|
||||
assertAcked(prepareCreate("test")
|
||||
.setSettings(indexSettings)
|
||||
.addMapping("parent")
|
||||
.addMapping("child", "_parent", "type=parent"));
|
||||
ensureGreen();
|
||||
|
||||
client().prepareIndex("test", "parent", "1").setSource("{}").get();
|
||||
client().prepareIndex("test", "child", "1").setParent("1").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get();
|
||||
long fielddataSizeDefault = response.getIndicesStats().getFieldData().getMemorySizeInBytes();
|
||||
assertThat(fielddataSizeDefault, greaterThan(0l));
|
||||
|
||||
PutMappingResponse putMappingResponse = client().admin().indices().preparePutMapping("test").setType("child")
|
||||
.setSource(childMapping(MappedFieldType.Loading.EAGER_GLOBAL_ORDINALS))
|
||||
.setUpdateAllTypes(true)
|
||||
.get();
|
||||
assertAcked(putMappingResponse);
|
||||
assertBusy(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClusterState clusterState = internalCluster().clusterService().state();
|
||||
ShardRouting shardRouting = clusterState.routingTable().index("test").shard(0).getShards().get(0);
|
||||
String nodeName = clusterState.getNodes().get(shardRouting.currentNodeId()).getName();
|
||||
|
||||
boolean verified = false;
|
||||
IndicesService indicesService = internalCluster().getInstance(IndicesService.class, nodeName);
|
||||
IndexService indexService = indicesService.indexService("test");
|
||||
if (indexService != null) {
|
||||
MapperService mapperService = indexService.mapperService();
|
||||
DocumentMapper documentMapper = mapperService.documentMapper("child");
|
||||
if (documentMapper != null) {
|
||||
verified = documentMapper.parentFieldMapper().getChildJoinFieldType().fieldDataType().getLoading() == MappedFieldType.Loading.EAGER_GLOBAL_ORDINALS;
|
||||
}
|
||||
}
|
||||
assertTrue(verified);
|
||||
}
|
||||
});
|
||||
|
||||
// Need to add a new doc otherwise the refresh doesn't trigger a new searcher
|
||||
// Because it ends up in its own segment, but isn't of type parent or child, this doc doesn't contribute to the size of the fielddata cache
|
||||
client().prepareIndex("test", "dummy", "dummy").setSource("{}").get();
|
||||
refresh();
|
||||
response = client().admin().cluster().prepareClusterStats().get();
|
||||
assertThat(response.getIndicesStats().getFieldData().getMemorySizeInBytes(), greaterThan(fielddataSizeDefault));
|
||||
}
|
||||
|
||||
private XContentBuilder childMapping(MappedFieldType.Loading loading) throws IOException {
|
||||
return jsonBuilder().startObject().startObject("child").startObject("_parent")
|
||||
.field("type", "parent")
|
||||
.startObject("fielddata").field(MappedFieldType.Loading.KEY, loading).endObject()
|
||||
.endObject().endObject().endObject();
|
||||
}
|
||||
|
||||
}
|
|
@ -63,3 +63,12 @@ Scroll requests sorted by `_doc` have been optimized to more efficiently resume
|
|||
from where the previous request stopped, so this will have the same performance
|
||||
characteristics as the former `scan` search type.
|
||||
|
||||
=== Parent/Child changes
|
||||
|
||||
The `children` aggregation, parent child inner hits and `has_child` and `has_parent` queries will not work on indices
|
||||
with `_parent` field mapping created before version `2.0.0`. The data of these indices need to be re-indexed into a new index.
|
||||
|
||||
The format of the join between parent and child documents have changed with the `2.0.0` release. The old
|
||||
format can't read from version `3.0.0` and onwards. The new format allows for a much more efficient and
|
||||
scalable join between parent and child documents and the join data structures are stored on on disk
|
||||
data structures as opposed as before the join data structures were stored in the jvm heap space.
|
Loading…
Reference in New Issue