Skip to content

Commit 6bc6881

Browse files
committed
Support has_child and has_parent queries.
Signed-off-by: Youssef Aouichaoui <youssef3wi@icloud.com>
1 parent ad66510 commit 6bc6881

File tree

7 files changed

+773
-1
lines changed

7 files changed

+773
-1
lines changed

src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
package org.springframework.data.elasticsearch.client.elc;
1717

1818
import static org.springframework.data.elasticsearch.client.elc.Queries.*;
19+
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.scoreMode;
1920
import static org.springframework.util.StringUtils.*;
2021

2122
import co.elastic.clients.elasticsearch._types.FieldValue;
2223
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
2324
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
2425
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
26+
import co.elastic.clients.elasticsearch.core.search.InnerHits;
2527
import co.elastic.clients.json.JsonData;
2628

2729
import java.util.ArrayList;
@@ -30,7 +32,12 @@
3032

3133
import org.springframework.data.elasticsearch.annotations.FieldType;
3234
import org.springframework.data.elasticsearch.core.query.Criteria;
35+
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
3336
import org.springframework.data.elasticsearch.core.query.Field;
37+
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
38+
import org.springframework.data.elasticsearch.core.query.HasParentQuery;
39+
import org.springframework.data.elasticsearch.core.query.InnerHitsQuery;
40+
import org.springframework.data.elasticsearch.core.query.StringQuery;
3441
import org.springframework.lang.Nullable;
3542
import org.springframework.util.Assert;
3643

@@ -343,6 +350,34 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
343350
.value(value.toString()) //
344351
.boost(boost)); //
345352
break;
353+
case HAS_CHILD:
354+
if (value instanceof HasChildQuery query) {
355+
queryBuilder.hasChild(hcb -> hcb
356+
.type(query.getType())
357+
.query(getQuery(query.getQuery()))
358+
.innerHits(getInnerHits(query.getInnerHitsQuery()))
359+
.ignoreUnmapped(query.getIgnoreUnmapped())
360+
.minChildren(query.getMinChildren())
361+
.maxChildren(query.getMaxChildren())
362+
.scoreMode(scoreMode(query.getScoreMode()))
363+
);
364+
} else {
365+
throw new CriteriaQueryException("value for " + fieldName + " is not a has_child query");
366+
}
367+
break;
368+
case HAS_PARENT:
369+
if (value instanceof HasParentQuery query) {
370+
queryBuilder.hasParent(hpb -> hpb
371+
.parentType(query.getParentType())
372+
.query(getQuery(query.getQuery()))
373+
.innerHits(getInnerHits(query.getInnerHitsQuery()))
374+
.ignoreUnmapped(query.getIgnoreUnmapped())
375+
.score(query.getScore())
376+
);
377+
} else {
378+
throw new CriteriaQueryException("value for " + fieldName + " is not a has_parent query");
379+
}
380+
break;
346381
default:
347382
throw new CriteriaQueryException("Could not build query for " + entry);
348383
}
@@ -397,4 +432,48 @@ public static String escape(String s) {
397432
return sb.toString();
398433
}
399434

435+
/**
436+
* Convert a spring-data-elasticsearch {@literal query} to an Elasticsearch {@literal query}.
437+
* <p>
438+
* The reason it was copied from {@link RequestConverter#getQuery(org.springframework.data.elasticsearch.core.query.Query, Class)} is because of a circular dependency.
439+
*
440+
* @param query spring-data-elasticsearch {@literal query}.
441+
* @return an Elasticsearch {@literal query}.
442+
*/
443+
private static Query getQuery(org.springframework.data.elasticsearch.core.query.Query query) {
444+
Query esQuery = null;
445+
446+
if (query instanceof CriteriaQuery) {
447+
esQuery = createQuery(((CriteriaQuery) query).getCriteria());
448+
} else if (query instanceof StringQuery) {
449+
esQuery = Queries.wrapperQueryAsQuery(((StringQuery) query).getSource());
450+
} else if (query instanceof NativeQuery nativeQuery) {
451+
if (nativeQuery.getQuery() != null) {
452+
esQuery = nativeQuery.getQuery();
453+
} else if (nativeQuery.getSpringDataQuery() != null) {
454+
esQuery = getQuery(nativeQuery.getSpringDataQuery());
455+
}
456+
} else {
457+
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
458+
}
459+
460+
Assert.notNull(query, "query must not be null.");
461+
return esQuery;
462+
}
463+
464+
/**
465+
* Convert a spring-data-elasticsearch {@literal inner_hits} to an Elasticsearch {@literal inner_hits} query.
466+
*
467+
* @param query spring-data-elasticsearch {@literal inner_hits}.
468+
* @return an Elasticsearch {@literal inner_hits} query.
469+
*/
470+
@Nullable
471+
private static InnerHits getInnerHits(@Nullable InnerHitsQuery query) {
472+
if (query == null) {
473+
return null;
474+
}
475+
476+
return InnerHits.of(iqb -> iqb.from(query.getFrom()).size(query.getSize()).name(query.getName()));
477+
}
478+
400479
}

src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import co.elastic.clients.elasticsearch._types.*;
1919
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
2020
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
21+
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
2122
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
2223
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
2324
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
@@ -41,6 +42,7 @@
4142
import org.springframework.data.elasticsearch.core.RefreshPolicy;
4243
import org.springframework.data.elasticsearch.core.document.Document;
4344
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
45+
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
4446
import org.springframework.data.elasticsearch.core.query.IndexQuery;
4547
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
4648
import org.springframework.data.elasticsearch.core.query.Order;
@@ -527,4 +529,24 @@ static Operator operator(@Nullable OperatorType operator) {
527529
static Conflicts conflicts(@Nullable ConflictsType conflicts) {
528530
return conflicts != null ? Conflicts.valueOf(conflicts.name()) : null;
529531
}
532+
533+
/**
534+
* Convert a spring-data-elasticsearch {@literal scoreMode} to an Elasticsearch {@literal scoreMode}.
535+
*
536+
* @param scoreMode spring-data-elasticsearch {@literal scoreMode}.
537+
* @return an Elasticsearch {@literal scoreMode}.
538+
*/
539+
static ChildScoreMode scoreMode(@Nullable HasChildQuery.ScoreMode scoreMode) {
540+
if (scoreMode == null) {
541+
return ChildScoreMode.None;
542+
}
543+
544+
return switch (scoreMode) {
545+
case Avg -> ChildScoreMode.Avg;
546+
case Max -> ChildScoreMode.Max;
547+
case Min -> ChildScoreMode.Min;
548+
case Sum -> ChildScoreMode.Sum;
549+
default -> ChildScoreMode.None;
550+
};
551+
}
530552
}

src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,32 @@ public Criteria contains(GeoJson<?> geoShape) {
816816
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.GEO_CONTAINS, geoShape));
817817
return this;
818818
}
819+
820+
/**
821+
* Adds a new filter CriteriaEntry for HAS_CHILD.
822+
*
823+
* @param query the has_child query.
824+
* @return the current Criteria.
825+
*/
826+
public Criteria hasChild(HasChildQuery query) {
827+
Assert.notNull(query, "has_child query must not be null.");
828+
829+
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_CHILD, query));
830+
return this;
831+
}
832+
833+
/**
834+
* Adds a new filter CriteriaEntry for HAS_PARENT.
835+
*
836+
* @param query the has_parent query.
837+
* @return the current Criteria.
838+
*/
839+
public Criteria hasParent(HasParentQuery query) {
840+
Assert.notNull(query, "has_parent query must not be null.");
841+
842+
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_PARENT, query));
843+
return this;
844+
}
819845
// endregion
820846

821847
// region helper functions
@@ -977,7 +1003,12 @@ public enum OperationKey { //
9771003
/**
9781004
* @since 5.1
9791005
*/
980-
REGEXP;
1006+
REGEXP,
1007+
/**
1008+
* @since 5.3
1009+
*/
1010+
HAS_CHILD,
1011+
HAS_PARENT;
9811012

9821013
/**
9831014
* @return true if this key does not have an associated value

0 commit comments

Comments
 (0)