Skip to content

Commit bfeb294

Browse files
dsmileyCopilot
andauthored
SOLR-14687: {!parent} and {!child} qparsers now use NestPathField (#4206)
The {!parent} and {!child} query parsers now support a parentPath local param that automatically derives the correct parent filter using the _nest_path_ field, making nested document queries easier to write correctly. childPath is also added as an optional param to limit the child-side of both queries. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dsmiley <377295+dsmiley@users.noreply.github.com>
1 parent ef75ab8 commit bfeb294

8 files changed

Lines changed: 588 additions & 79 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: The {!parent} and {!child} query parsers now support a parentPath local param that automatically derives the correct parent filter using the _nest_path_ field, making nested document queries easier to write correctly. childPath is also added.
3+
type: added # added, changed, fixed, deprecated, removed, dependency_update, security, other
4+
authors:
5+
- name: David Smiley
6+
- name: hossman
7+
links:
8+
- name: SOLR-14687
9+
url: https://issues.apache.org/jira/browse/SOLR-14687

solr/core/src/java/org/apache/solr/search/join/BlockJoinChildQParser.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.solr.request.SolrQueryRequest;
2626
import org.apache.solr.search.SyntaxError;
2727

28+
/** Matches child documents based on parent doc criteria. */
2829
public class BlockJoinChildQParser extends BlockJoinParentQParser {
2930

3031
public BlockJoinChildQParser(
@@ -33,23 +34,76 @@ public BlockJoinChildQParser(
3334
}
3435

3536
@Override
36-
protected Query createQuery(Query parentListQuery, Query query, String scoreMode) {
37-
return new ToChildBlockJoinQuery(query, getBitSetProducer(parentListQuery));
37+
protected Query createQuery(Query parentListQuery, Query fromQuery, String scoreMode) {
38+
return new ToChildBlockJoinQuery(fromQuery, getBitSetProducer(parentListQuery));
3839
}
3940

4041
@Override
41-
protected String getParentFilterLocalParamName() {
42+
protected String getLegacyParentFilterParamName() {
4243
return "of";
4344
}
4445

4546
@Override
46-
protected Query noClausesQuery() throws SyntaxError {
47-
final Query parents = parseParentFilter();
47+
protected Query noClausesQueryLegacy() throws SyntaxError {
48+
final Query parents = parseLegacyParentFilter();
4849
final BooleanQuery notParents =
4950
new BooleanQuery.Builder()
5051
.add(new MatchAllDocsQuery(), Occur.MUST)
5152
.add(parents, Occur.MUST_NOT)
5253
.build();
5354
return new BitSetProducerQuery(getBitSetProducer(notParents));
5455
}
56+
57+
/**
58+
* Parses the query using the {@code parentPath} local-param for the child parser.
59+
*
60+
* <p>For the {@code child} parser with {@code parentPath="/a/b/c"}:
61+
*
62+
* <pre>NEW: q={!child parentPath="/a/b/c"}p_title:dad
63+
*
64+
* OLD: q={!child of=$ff v=$vv}
65+
* ff=(*:* -{!prefix f="_nest_path_" v="/a/b/c/"})
66+
* vv=(+p_title:dad +{!field f="_nest_path_" v="/a/b/c"})</pre>
67+
*
68+
* <p>For {@code parentPath="/"}:
69+
*
70+
* <pre>NEW: q={!child parentPath="/"}p_title:dad
71+
*
72+
* OLD: q={!child of=$ff v=$vv}
73+
* ff=(*:* -_nest_path_:*)
74+
* vv=(+p_title:dad -_nest_path_:*)</pre>
75+
*
76+
* <p>The optional {@code childPath} local-param narrows the returned children to docs at exactly
77+
* {@code parentPath/childPath}.
78+
*
79+
* @param parentPath the normalized parent path (starts with "/", no trailing slash except for
80+
* root "/")
81+
* @param childPath optional path constraining the children relative to parentPath
82+
*/
83+
@Override
84+
protected Query parseUsingParentPath(String parentPath, String childPath) throws SyntaxError {
85+
final BooleanQuery parsedParentQuery = parseImpl();
86+
87+
if (parsedParentQuery.clauses().isEmpty()) { // i.e. match all parents
88+
// no block-join needed; just filter to certain children
89+
return wrapWithChildPathConstraint(parentPath, childPath, new MatchAllDocsQuery());
90+
}
91+
92+
// allParents filter: (*:* -{!prefix f="_nest_path_" v="<parentPath>/"})
93+
// For root: (*:* -_nest_path_:*)
94+
final Query allParentsFilter = buildAllParentsFilterFromPath(parentPath);
95+
96+
// constrain the parent query to only match docs at exactly parentPath
97+
// (+<original_parent> +{!field f="_nest_path_" v="<parentPath>"})
98+
// For root: (+<original_parent> -_nest_path_:*)
99+
Query constrainedParentQuery = wrapWithParentPathConstraint(parentPath, parsedParentQuery);
100+
101+
Query joinQuery = createQuery(allParentsFilter, constrainedParentQuery, null);
102+
// matches all children of matching parents
103+
if (childPath == null) {
104+
return joinQuery;
105+
}
106+
// need to constrain to certain children
107+
return wrapWithChildPathConstraint(parentPath, childPath, joinQuery);
108+
}
55109
}

0 commit comments

Comments
 (0)