Skip to content

Commit

Permalink
Merge pull request #93 from Paul-Blanchaert/querqy_decorations_with_aggs
Browse files Browse the repository at this point in the history
Querqy decorations with aggs
  • Loading branch information
renekrie authored Mar 14, 2024
2 parents 60a2f5b + 5c09ccf commit b1c9a0b
Show file tree
Hide file tree
Showing 12 changed files with 926 additions and 24 deletions.
33 changes: 25 additions & 8 deletions src/main/java/querqy/elasticsearch/QuerqyPlugin.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package querqy.elasticsearch;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static querqy.elasticsearch.rewriterstore.Constants.SETTINGS_QUERQY_INDEX_NUM_REPLICAS;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.client.Client;
Expand All @@ -16,7 +12,6 @@
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.IndexModule;
Expand All @@ -30,25 +25,33 @@
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import querqy.elasticsearch.aggregation.InternalDecorationAggregation;
import querqy.elasticsearch.aggregation.QuerqyDecorationAggregationBuilder;
import querqy.elasticsearch.infologging.Log4jSink;
import querqy.elasticsearch.query.QuerqyQueryBuilder;
import querqy.elasticsearch.rewriterstore.DeleteRewriterAction;
import querqy.elasticsearch.rewriterstore.NodesClearRewriterCacheAction;
import querqy.elasticsearch.rewriterstore.NodesReloadRewriterAction;
import querqy.elasticsearch.rewriterstore.PutRewriterAction;
import querqy.elasticsearch.rewriterstore.RestDeleteRewriterAction;
import querqy.elasticsearch.rewriterstore.RestPutRewriterAction;
import querqy.elasticsearch.rewriterstore.PutRewriterAction;
import querqy.elasticsearch.rewriterstore.TransportDeleteRewriterAction;
import querqy.elasticsearch.rewriterstore.TransportNodesClearRewriterCacheAction;
import querqy.elasticsearch.rewriterstore.TransportNodesReloadRewriterAction;
import querqy.elasticsearch.rewriterstore.TransportPutRewriterAction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static querqy.elasticsearch.rewriterstore.Constants.SETTINGS_QUERQY_INDEX_NUM_REPLICAS;

public class QuerqyPlugin extends Plugin implements SearchPlugin, ActionPlugin {


Expand Down Expand Up @@ -111,8 +114,8 @@ public Collection<Object> createComponents(final Client client, final ClusterSer
final NamedXContentRegistry xContentRegistry,
final Environment environment, final NodeEnvironment nodeEnvironment,
final NamedWriteableRegistry namedWriteableRegistry,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier) {
final IndexNameExpressionResolver indexNameExpressionResolver,
final Supplier<RepositoriesService> repositoriesServiceSupplier) {
return Arrays.asList(rewriterShardContexts, querqyProcessor);
}

Expand All @@ -122,4 +125,18 @@ public List<Setting<?>> getSettings() {
Setting.Property.NodeScope));

}

@Override
public List<AggregationSpec> getAggregations() {
final List<AggregationSpec> r = new ArrayList<>();
r.add(
new AggregationSpec(
QuerqyDecorationAggregationBuilder.NAME,
QuerqyDecorationAggregationBuilder::new,
QuerqyDecorationAggregationBuilder.PARSER
).addResultReader(InternalDecorationAggregation::new)
);
return r;
}

}
14 changes: 8 additions & 6 deletions src/main/java/querqy/elasticsearch/QuerqyProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.index.query.SearchExecutionContext;
import querqy.elasticsearch.aggregation.DecoratedQuery;
import querqy.elasticsearch.infologging.LogPayloadType;
import querqy.elasticsearch.infologging.SingleSinkInfoLogging;
import querqy.elasticsearch.query.InfoLoggingSpec;
Expand All @@ -15,6 +16,7 @@
import querqy.lucene.LuceneSearchEngineRequestAdapter;
import querqy.lucene.QueryParsingController;
import querqy.rewrite.RewriteChain;
import querqy.rewrite.commonrules.model.DecorateInstruction;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -75,10 +77,6 @@ public Query parseQuery(final QuerqyQueryBuilder queryBuilder, final SearchExecu
final QueryParsingController controller = new QueryParsingController(requestAdapter);
final LuceneQueries queries = controller.process();


// // TODO: make decos part of the general Querqy object model
// final Set<Object> decorations = (Set<Object>) requestAdapter.getContext().get(DecorateInstruction.CONTEXT_KEY);

if ((queries.querqyBoostQueries == null || queries.querqyBoostQueries.isEmpty())
&& (queries.filterQueries == null || queries.filterQueries.isEmpty())
&& queries.mainQuery instanceof BooleanQuery) {
Expand All @@ -104,15 +102,19 @@ public Query parseQuery(final QuerqyQueryBuilder queryBuilder, final SearchExecu

appendFilterQueries(queries, builder);

final BooleanQuery query = builder.build();
final Set<Object> decorations = (Set<Object>) requestAdapter.getContext().get(DecorateInstruction.DECORATION_CONTEXT_KEY);
final Query query =
decorations != null && !decorations.isEmpty() ?
new DecoratedQuery<>(builder.build(), decorations) :
builder.build();
if (infoLogging != null) {
infoLogging.endOfRequest(requestAdapter);
}

return query;

}


void appendFilterQueries(final LuceneQueries queries, final BooleanQuery.Builder builder) {

if (queries.filterQueries != null) {
Expand Down
83 changes: 83 additions & 0 deletions src/main/java/querqy/elasticsearch/aggregation/DecoratedQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package querqy.elasticsearch.aggregation;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Weight;

import java.io.IOException;
import java.util.Objects;
import java.util.Set;

public class DecoratedQuery<T extends Query> extends Query {

final private T query;
final private Set<Object> decorations;

public DecoratedQuery(final T query, final Set<Object> decorations) {
this.query = Objects.requireNonNull(query);
this.decorations = Objects.requireNonNull(decorations);
}

public T getQuery() {
return query;
}

public Set<Object> getDecorations() {
return decorations;
}

@Override
public Weight createWeight(final IndexSearcher searcher, final ScoreMode scoreMode, final float boost) throws IOException {
return query.createWeight(searcher, scoreMode, boost);
}

@Override
public Query rewrite(final IndexReader reader) throws IOException {
return query.rewrite(reader);
}

@Override
public String toString(final String field) {
return query.toString(field);
}

@Override
public boolean equals(final Object object) {
if (!sameClassAs(object)) return false;
final DecoratedQuery<?> other = castObject(object);
return isEqualQueriesAndDecorations(other);
}

private boolean isEqualQueriesAndDecorations(final DecoratedQuery<?> other) {
final Query otherQuery = other.getQuery();
final Set<Object> otherDecorations = other.getDecorations();
return getQuery().equals(otherQuery) && getDecorations().equals(otherDecorations);
}

private DecoratedQuery<?> castObject(final Object object) {
return getClass().cast(object);
}

private int computeHashCode() {
int hashCode = Objects.hash(query, decorations);
if (hashCode == 0) {
hashCode = 1;
}
return hashCode;
}

// cached hash code is ok since boolean queries are immutable
private int hashCode;

@Override
public int hashCode() {
// no need for synchronization, in the worst case we would just compute the hash several times.
if (hashCode == 0) {
hashCode = computeHashCode();
}
return hashCode;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package querqy.elasticsearch.aggregation;

import org.elasticsearch.search.aggregations.Aggregation;

/**
* A {@code DecorationAggregation} aggregation. Defines a single bucket the holds all the querqy info in the search context.
*/
public interface DecorationAggregation extends Aggregation {

/**
* The result of the aggregation. The type of the object depends on the aggregation that was run.
*/
Object aggregation();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package querqy.elasticsearch.aggregation;

import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.aggregations.metrics.ScriptedMetric;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Collections.singletonList;

public class InternalDecorationAggregation extends InternalAggregation implements ScriptedMetric {

private final List<Object> aggregations;

InternalDecorationAggregation(final String name, final List<Object> aggregations, final Map<String, Object> metadata) {
super(name, metadata);
this.aggregations = aggregations;
}

public InternalDecorationAggregation(final StreamInput in) throws IOException {
super(in);
aggregations = in.readList(StreamInput::readGenericValue);
}

@Override
protected void doWriteTo(final StreamOutput out) throws IOException {
out.writeCollection(aggregations, StreamOutput::writeGenericValue);
}

@Override
public String getWriteableName() {
return QuerqyDecorationAggregationBuilder.NAME;
}

@Override
public Object aggregation() {
if (aggregations.size() != 1) {
throw new IllegalStateException("aggregation was not reduced");
}
return aggregations.get(0);
}

List<Object> aggregationsList() {
return aggregations;
}

@Override
public InternalAggregation reduce(final List<InternalAggregation> aggregations, final ReduceContext reduceContext) {
final List<Object> aggregationObjects = new ArrayList<>();
for (final InternalAggregation aggregation : aggregations) {
final InternalDecorationAggregation mapReduceAggregation = (InternalDecorationAggregation) aggregation;
aggregationObjects.addAll(mapReduceAggregation.aggregations);
}
final InternalDecorationAggregation firstAggregation = ((InternalDecorationAggregation) aggregations.get(0));
final List<Object> aggregation;
if (reduceContext.isFinalReduce()) {
aggregation = Collections.singletonList(aggregationObjects);
} else {
// if we are not an final reduce we have to maintain all the aggs from all the incoming one
// until we hit the final reduce phase.
aggregation = aggregationObjects;
}
return new InternalDecorationAggregation(firstAggregation.getName(), aggregation, getMetadata());
}

@Override
protected boolean mustReduceOnSingleInternalAgg() {
return true;
}

@Override
public Object getProperty(final List<String> path) {
if (path.isEmpty()) {
return this;
} else if (path.size() == 1 && "value".equals(path.get(0))) {
return aggregation();
} else {
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
}
}

@Override
public XContentBuilder doXContentBody(final XContentBuilder builder, final Params params) throws IOException {
return builder.field(CommonFields.VALUE.getPreferredName(), aggregation());
}

@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
if (!super.equals(obj)) return false;

final InternalDecorationAggregation other = (InternalDecorationAggregation) obj;
return Objects.equals(aggregations, other.aggregations);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), aggregations);
}

}
Loading

0 comments on commit b1c9a0b

Please sign in to comment.