Skip to content

Commit bf06726

Browse files
opensearch-trigger-bot[bot]github-actions[bot]msfroh
authored
[Backport 2.x] Show only intersecting buckets to the Adjacency matrix aggregation (#17006)
* Show only intersecting buckets to the Adjacency matrix aggregation (#11733) Signed-off-by: Ivan Brusic <ivan@brusic.com> (cherry picked from commit 8d5e1a3) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Update version checks for backport Signed-off-by: Michael Froh <froh@amazon.com> --------- Signed-off-by: Ivan Brusic <ivan@brusic.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Signed-off-by: Michael Froh <froh@amazon.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Michael Froh <froh@amazon.com>
1 parent 0f8b032 commit bf06726

File tree

6 files changed

+177
-16
lines changed

6 files changed

+177
-16
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/70_adjacency_matrix.yml

+37
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,40 @@ setup:
125125

126126
- match: { aggregations.conns.buckets.3.doc_count: 1 }
127127
- match: { aggregations.conns.buckets.3.key: "4" }
128+
129+
130+
---
131+
"Show only intersections":
132+
- skip:
133+
version: " - 2.19.0"
134+
reason: "show_only_intersecting was added in 2.19.0"
135+
features: node_selector
136+
- do:
137+
node_selector:
138+
version: "2.19.0 - "
139+
search:
140+
index: test
141+
rest_total_hits_as_int: true
142+
body:
143+
size: 0
144+
aggs:
145+
conns:
146+
adjacency_matrix:
147+
show_only_intersecting: true
148+
filters:
149+
1:
150+
term:
151+
num: 1
152+
2:
153+
term:
154+
num: 2
155+
4:
156+
term:
157+
num: 4
158+
159+
- match: { hits.total: 3 }
160+
161+
- length: { aggregations.conns.buckets: 1 }
162+
163+
- match: { aggregations.conns.buckets.0.doc_count: 1 }
164+
- match: { aggregations.conns.buckets.0.key: "1&2" }

server/src/main/java/org/opensearch/search/aggregations/bucket/adjacency/AdjacencyMatrixAggregationBuilder.java

+77-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
package org.opensearch.search.aggregations.bucket.adjacency;
3434

35+
import org.opensearch.Version;
3536
import org.opensearch.core.ParseField;
3637
import org.opensearch.core.common.io.stream.StreamInput;
3738
import org.opensearch.core.common.io.stream.StreamOutput;
@@ -71,7 +72,10 @@ public class AdjacencyMatrixAggregationBuilder extends AbstractAggregationBuilde
7172

7273
private static final ParseField SEPARATOR_FIELD = new ParseField("separator");
7374
private static final ParseField FILTERS_FIELD = new ParseField("filters");
75+
private static final ParseField SHOW_ONLY_INTERSECTING = new ParseField("show_only_intersecting");
76+
7477
private List<KeyedFilter> filters;
78+
private boolean showOnlyIntersecting = false;
7579
private String separator = DEFAULT_SEPARATOR;
7680

7781
private static final ObjectParser<AdjacencyMatrixAggregationBuilder, String> PARSER = ObjectParser.fromBuilder(
@@ -81,6 +85,10 @@ public class AdjacencyMatrixAggregationBuilder extends AbstractAggregationBuilde
8185
static {
8286
PARSER.declareString(AdjacencyMatrixAggregationBuilder::separator, SEPARATOR_FIELD);
8387
PARSER.declareNamedObjects(AdjacencyMatrixAggregationBuilder::setFiltersAsList, KeyedFilter.PARSER, FILTERS_FIELD);
88+
PARSER.declareBoolean(
89+
AdjacencyMatrixAggregationBuilder::setShowOnlyIntersecting,
90+
AdjacencyMatrixAggregationBuilder.SHOW_ONLY_INTERSECTING
91+
);
8492
}
8593

8694
public static AggregationBuilder parse(XContentParser parser, String name) throws IOException {
@@ -115,6 +123,7 @@ protected AdjacencyMatrixAggregationBuilder(
115123
super(clone, factoriesBuilder, metadata);
116124
this.filters = new ArrayList<>(clone.filters);
117125
this.separator = clone.separator;
126+
this.showOnlyIntersecting = clone.showOnlyIntersecting;
118127
}
119128

120129
@Override
@@ -138,13 +147,50 @@ public AdjacencyMatrixAggregationBuilder(String name, String separator, Map<Stri
138147
setFiltersAsMap(filters);
139148
}
140149

150+
/**
151+
* @param name
152+
* the name of this aggregation
153+
* @param filters
154+
* the filters and their key to use with this aggregation.
155+
* @param showOnlyIntersecting
156+
* show only the buckets that intersection multiple documents
157+
*/
158+
public AdjacencyMatrixAggregationBuilder(String name, Map<String, QueryBuilder> filters, boolean showOnlyIntersecting) {
159+
this(name, DEFAULT_SEPARATOR, filters, showOnlyIntersecting);
160+
}
161+
162+
/**
163+
* @param name
164+
* the name of this aggregation
165+
* @param separator
166+
* the string used to separate keys in intersections buckets e.g.
167+
* &amp; character for keyed filters A and B would return an
168+
* intersection bucket named A&amp;B
169+
* @param filters
170+
* the filters and their key to use with this aggregation.
171+
* @param showOnlyIntersecting
172+
* show only the buckets that intersection multiple documents
173+
*/
174+
public AdjacencyMatrixAggregationBuilder(
175+
String name,
176+
String separator,
177+
Map<String, QueryBuilder> filters,
178+
boolean showOnlyIntersecting
179+
) {
180+
this(name, separator, filters);
181+
this.showOnlyIntersecting = showOnlyIntersecting;
182+
}
183+
141184
/**
142185
* Read from a stream.
143186
*/
144187
public AdjacencyMatrixAggregationBuilder(StreamInput in) throws IOException {
145188
super(in);
146189
int filtersSize = in.readVInt();
147190
separator = in.readString();
191+
if (in.getVersion().onOrAfter(Version.V_2_19_0)) {
192+
showOnlyIntersecting = in.readBoolean();
193+
}
148194
filters = new ArrayList<>(filtersSize);
149195
for (int i = 0; i < filtersSize; i++) {
150196
filters.add(new KeyedFilter(in));
@@ -155,6 +201,9 @@ public AdjacencyMatrixAggregationBuilder(StreamInput in) throws IOException {
155201
protected void doWriteTo(StreamOutput out) throws IOException {
156202
out.writeVInt(filters.size());
157203
out.writeString(separator);
204+
if (out.getVersion().onOrAfter(Version.V_2_19_0)) {
205+
out.writeBoolean(showOnlyIntersecting);
206+
}
158207
for (KeyedFilter keyedFilter : filters) {
159208
keyedFilter.writeTo(out);
160209
}
@@ -185,6 +234,11 @@ private AdjacencyMatrixAggregationBuilder setFiltersAsList(List<KeyedFilter> fil
185234
return this;
186235
}
187236

237+
public AdjacencyMatrixAggregationBuilder setShowOnlyIntersecting(boolean showOnlyIntersecting) {
238+
this.showOnlyIntersecting = showOnlyIntersecting;
239+
return this;
240+
}
241+
188242
/**
189243
* Set the separator used to join pairs of bucket keys
190244
*/
@@ -214,6 +268,10 @@ public Map<String, QueryBuilder> filters() {
214268
return result;
215269
}
216270

271+
public boolean isShowOnlyIntersecting() {
272+
return showOnlyIntersecting;
273+
}
274+
217275
@Override
218276
protected AdjacencyMatrixAggregationBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException {
219277
boolean modified = false;
@@ -224,7 +282,9 @@ protected AdjacencyMatrixAggregationBuilder doRewrite(QueryRewriteContext queryS
224282
rewrittenFilters.add(new KeyedFilter(kf.key(), rewritten));
225283
}
226284
if (modified) {
227-
return new AdjacencyMatrixAggregationBuilder(name).separator(separator).setFiltersAsList(rewrittenFilters);
285+
return new AdjacencyMatrixAggregationBuilder(name).separator(separator)
286+
.setFiltersAsList(rewrittenFilters)
287+
.setShowOnlyIntersecting(showOnlyIntersecting);
228288
}
229289
return this;
230290
}
@@ -245,7 +305,16 @@ protected AggregatorFactory doBuild(QueryShardContext queryShardContext, Aggrega
245305
+ "] index level setting."
246306
);
247307
}
248-
return new AdjacencyMatrixAggregatorFactory(name, filters, separator, queryShardContext, parent, subFactoriesBuilder, metadata);
308+
return new AdjacencyMatrixAggregatorFactory(
309+
name,
310+
filters,
311+
showOnlyIntersecting,
312+
separator,
313+
queryShardContext,
314+
parent,
315+
subFactoriesBuilder,
316+
metadata
317+
);
249318
}
250319

251320
@Override
@@ -257,7 +326,8 @@ public BucketCardinality bucketCardinality() {
257326
protected XContentBuilder internalXContent(XContentBuilder builder, Params params) throws IOException {
258327
builder.startObject();
259328
builder.field(SEPARATOR_FIELD.getPreferredName(), separator);
260-
builder.startObject(AdjacencyMatrixAggregator.FILTERS_FIELD.getPreferredName());
329+
builder.field(SHOW_ONLY_INTERSECTING.getPreferredName(), showOnlyIntersecting);
330+
builder.startObject(FILTERS_FIELD.getPreferredName());
261331
for (KeyedFilter keyedFilter : filters) {
262332
builder.field(keyedFilter.key(), keyedFilter.filter());
263333
}
@@ -268,7 +338,7 @@ protected XContentBuilder internalXContent(XContentBuilder builder, Params param
268338

269339
@Override
270340
public int hashCode() {
271-
return Objects.hash(super.hashCode(), filters, separator);
341+
return Objects.hash(super.hashCode(), filters, showOnlyIntersecting, separator);
272342
}
273343

274344
@Override
@@ -277,7 +347,9 @@ public boolean equals(Object obj) {
277347
if (obj == null || getClass() != obj.getClass()) return false;
278348
if (super.equals(obj) == false) return false;
279349
AdjacencyMatrixAggregationBuilder other = (AdjacencyMatrixAggregationBuilder) obj;
280-
return Objects.equals(filters, other.filters) && Objects.equals(separator, other.separator);
350+
return Objects.equals(filters, other.filters)
351+
&& Objects.equals(separator, other.separator)
352+
&& Objects.equals(showOnlyIntersecting, other.showOnlyIntersecting);
281353
}
282354

283355
@Override

server/src/main/java/org/opensearch/search/aggregations/bucket/adjacency/AdjacencyMatrixAggregator.java

+11-8
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.apache.lucene.search.Weight;
3737
import org.apache.lucene.util.Bits;
3838
import org.opensearch.common.lucene.Lucene;
39-
import org.opensearch.core.ParseField;
4039
import org.opensearch.core.common.io.stream.StreamInput;
4140
import org.opensearch.core.common.io.stream.StreamOutput;
4241
import org.opensearch.core.common.io.stream.Writeable;
@@ -70,8 +69,6 @@
7069
*/
7170
public class AdjacencyMatrixAggregator extends BucketsAggregator {
7271

73-
public static final ParseField FILTERS_FIELD = new ParseField("filters");
74-
7572
/**
7673
* A keyed filter
7774
*
@@ -145,6 +142,8 @@ public boolean equals(Object obj) {
145142

146143
private final String[] keys;
147144
private final Weight[] filters;
145+
146+
private final boolean showOnlyIntersecting;
148147
private final int totalNumKeys;
149148
private final int totalNumIntersections;
150149
private final String separator;
@@ -155,6 +154,7 @@ public AdjacencyMatrixAggregator(
155154
String separator,
156155
String[] keys,
157156
Weight[] filters,
157+
boolean showOnlyIntersecting,
158158
SearchContext context,
159159
Aggregator parent,
160160
Map<String, Object> metadata
@@ -163,6 +163,7 @@ public AdjacencyMatrixAggregator(
163163
this.separator = separator;
164164
this.keys = keys;
165165
this.filters = filters;
166+
this.showOnlyIntersecting = showOnlyIntersecting;
166167
this.totalNumIntersections = ((keys.length * keys.length) - keys.length) / 2;
167168
this.totalNumKeys = keys.length + totalNumIntersections;
168169
}
@@ -177,10 +178,12 @@ public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, final LeafBuc
177178
return new LeafBucketCollectorBase(sub, null) {
178179
@Override
179180
public void collect(int doc, long bucket) throws IOException {
180-
// Check each of the provided filters
181-
for (int i = 0; i < bits.length; i++) {
182-
if (bits[i].get(doc)) {
183-
collectBucket(sub, doc, bucketOrd(bucket, i));
181+
if (!showOnlyIntersecting) {
182+
// Check each of the provided filters
183+
for (int i = 0; i < bits.length; i++) {
184+
if (bits[i].get(doc)) {
185+
collectBucket(sub, doc, bucketOrd(bucket, i));
186+
}
184187
}
185188
}
186189
// Check all the possible intersections of the provided filters
@@ -229,7 +232,7 @@ public InternalAggregation[] buildAggregations(long[] owningBucketOrds) throws I
229232
for (int i = 0; i < keys.length; i++) {
230233
long bucketOrd = bucketOrd(owningBucketOrds[owningBucketOrdIdx], i);
231234
long docCount = bucketDocCount(bucketOrd);
232-
// Empty buckets are not returned because this aggregation will commonly be used under a
235+
// Empty buckets are not returned because this aggregation will commonly be used under
233236
// a date-histogram where we will look for transactions over time and can expect many
234237
// empty buckets.
235238
if (docCount > 0) {

server/src/main/java/org/opensearch/search/aggregations/bucket/adjacency/AdjacencyMatrixAggregatorFactory.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ public class AdjacencyMatrixAggregatorFactory extends AggregatorFactory {
5757

5858
private final String[] keys;
5959
private final Weight[] weights;
60+
61+
private final boolean showOnlyIntersecting;
6062
private final String separator;
6163

6264
public AdjacencyMatrixAggregatorFactory(
6365
String name,
6466
List<KeyedFilter> filters,
67+
boolean showOnlyIntersecting,
6568
String separator,
6669
QueryShardContext queryShardContext,
6770
AggregatorFactory parent,
@@ -79,6 +82,7 @@ public AdjacencyMatrixAggregatorFactory(
7982
Query filter = keyedFilter.filter().toQuery(queryShardContext);
8083
weights[i] = contextSearcher.createWeight(contextSearcher.rewrite(filter), ScoreMode.COMPLETE_NO_SCORES, 1f);
8184
}
85+
this.showOnlyIntersecting = showOnlyIntersecting;
8286
}
8387

8488
@Override
@@ -88,7 +92,17 @@ public Aggregator createInternal(
8892
CardinalityUpperBound cardinality,
8993
Map<String, Object> metadata
9094
) throws IOException {
91-
return new AdjacencyMatrixAggregator(name, factories, separator, keys, weights, searchContext, parent, metadata);
95+
return new AdjacencyMatrixAggregator(
96+
name,
97+
factories,
98+
separator,
99+
keys,
100+
weights,
101+
showOnlyIntersecting,
102+
searchContext,
103+
parent,
104+
metadata
105+
);
92106
}
93107

94108
@Override

server/src/test/java/org/opensearch/search/aggregations/bucket/adjacency/AdjacencyMatrixAggregationBuilderTests.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
public class AdjacencyMatrixAggregationBuilderTests extends OpenSearchTestCase {
5858

5959
public void testFilterSizeLimitation() throws Exception {
60-
// filter size grater than max size should thrown a exception
60+
// filter size grater than max size should throw an exception
6161
QueryShardContext queryShardContext = mock(QueryShardContext.class);
6262
IndexShard indexShard = mock(IndexShard.class);
6363
Settings settings = Settings.builder()
@@ -94,7 +94,7 @@ public void testFilterSizeLimitation() throws Exception {
9494
)
9595
);
9696

97-
// filter size not grater than max size should return an instance of AdjacencyMatrixAggregatorFactory
97+
// filter size not greater than max size should return an instance of AdjacencyMatrixAggregatorFactory
9898
Map<String, QueryBuilder> emptyFilters = Collections.emptyMap();
9999

100100
AdjacencyMatrixAggregationBuilder aggregationBuilder = new AdjacencyMatrixAggregationBuilder("dummy", emptyFilters);
@@ -106,4 +106,21 @@ public void testFilterSizeLimitation() throws Exception {
106106
+ "removed in a future release! See the breaking changes documentation for the next major version."
107107
);
108108
}
109+
110+
public void testShowOnlyIntersecting() throws Exception {
111+
QueryShardContext queryShardContext = mock(QueryShardContext.class);
112+
113+
Map<String, QueryBuilder> filters = new HashMap<>(3);
114+
for (int i = 0; i < 2; i++) {
115+
QueryBuilder queryBuilder = mock(QueryBuilder.class);
116+
// return builder itself to skip rewrite
117+
when(queryBuilder.rewrite(queryShardContext)).thenReturn(queryBuilder);
118+
filters.put("filter" + i, queryBuilder);
119+
}
120+
AdjacencyMatrixAggregationBuilder builder = new AdjacencyMatrixAggregationBuilder("dummy", filters, true);
121+
assertTrue(builder.isShowOnlyIntersecting());
122+
123+
builder = new AdjacencyMatrixAggregationBuilder("dummy", filters, false);
124+
assertFalse(builder.isShowOnlyIntersecting());
125+
}
109126
}

server/src/test/java/org/opensearch/search/aggregations/metrics/AdjacencyMatrixTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,22 @@ public void testFiltersSameMap() {
6868
assertEquals(original, builder.filters());
6969
assert original != builder.filters();
7070
}
71+
72+
public void testShowOnlyIntersecting() {
73+
Map<String, QueryBuilder> original = new HashMap<>();
74+
original.put("bbb", new MatchNoneQueryBuilder());
75+
original.put("aaa", new MatchNoneQueryBuilder());
76+
AdjacencyMatrixAggregationBuilder builder;
77+
builder = new AdjacencyMatrixAggregationBuilder("my-agg", "&", original, true);
78+
assertTrue(builder.isShowOnlyIntersecting());
79+
}
80+
81+
public void testShowOnlyIntersectingAsFalse() {
82+
Map<String, QueryBuilder> original = new HashMap<>();
83+
original.put("bbb", new MatchNoneQueryBuilder());
84+
original.put("aaa", new MatchNoneQueryBuilder());
85+
AdjacencyMatrixAggregationBuilder builder;
86+
builder = new AdjacencyMatrixAggregationBuilder("my-agg", original, false);
87+
assertFalse(builder.isShowOnlyIntersecting());
88+
}
7189
}

0 commit comments

Comments
 (0)