From e306ef97f42eb959a075a1385d5fc0cf581c74e6 Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Tue, 9 Jun 2026 12:40:06 +0530 Subject: [PATCH 1/8] Initial Commit --- .../SOLR_18197_nest_path_easy_of_use.yml | 7 +++++++ .../org/apache/solr/schema/NestPathField.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml diff --git a/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml b/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml new file mode 100644 index 000000000000..f45915ddea4b --- /dev/null +++ b/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml @@ -0,0 +1,7 @@ +title: Add root document query shortcut support to NestPathField +type: changed +authors: + - name: Abhishek Umarjikar +links: + - name: SOLR-18197 + url: https://issues.apache.org/jira/browse/SOLR-18197 diff --git a/solr/core/src/java/org/apache/solr/schema/NestPathField.java b/solr/core/src/java/org/apache/solr/schema/NestPathField.java index d34e532abbb5..b20a61c9ef7e 100644 --- a/solr/core/src/java/org/apache/solr/schema/NestPathField.java +++ b/solr/core/src/java/org/apache/solr/schema/NestPathField.java @@ -22,8 +22,13 @@ import org.apache.lucene.analysis.core.KeywordTokenizerFactory; import org.apache.lucene.analysis.custom.CustomAnalyzer; import org.apache.lucene.analysis.pattern.PatternReplaceFilterFactory; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; import org.apache.solr.analysis.TokenizerChain; import org.apache.solr.common.SolrException; +import org.apache.solr.search.QParser; /** * To be used for field {@link IndexSchema#NEST_PATH_FIELD_NAME} for enhanced nested doc @@ -62,4 +67,16 @@ public void setArgs(IndexSchema schema, Map args) { setIndexAnalyzer(new TokenizerChain(customAnalyzer)); // leave queryAnalyzer as literal } + + @Override + public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) { + if (externalVal == null || externalVal.isEmpty() || "/".equals(externalVal)) { + return new BooleanQuery.Builder() + .add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST) + .add(field.getType().getExistenceQuery(parser, field), BooleanClause.Occur.MUST_NOT) + .build(); + } + + return super.getFieldQuery(parser, field, externalVal); + } } From cdb63fddee848a387caedfc013f80ca7b027d45b Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Wed, 10 Jun 2026 11:57:25 +0530 Subject: [PATCH 2/8] Changed the type as suggested. --- changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml b/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml index f45915ddea4b..b2b1678da6cb 100644 --- a/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml +++ b/changelog/unreleased/SOLR_18197_nest_path_easy_of_use.yml @@ -1,7 +1,8 @@ title: Add root document query shortcut support to NestPathField -type: changed +type: added authors: - name: Abhishek Umarjikar + nick: abumarjikar links: - name: SOLR-18197 url: https://issues.apache.org/jira/browse/SOLR-18197 From f3c86ffb9c0b381e3cc1ac8f8259908d0d53534b Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Thu, 11 Jun 2026 13:10:34 +0530 Subject: [PATCH 3/8] Added Test and also null handling with bad request --- .../org/apache/solr/schema/NestPathField.java | 8 +- .../apache/solr/schema/TestNestPathField.java | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 solr/core/src/test/org/apache/solr/schema/TestNestPathField.java diff --git a/solr/core/src/java/org/apache/solr/schema/NestPathField.java b/solr/core/src/java/org/apache/solr/schema/NestPathField.java index b20a61c9ef7e..bf064a4da28e 100644 --- a/solr/core/src/java/org/apache/solr/schema/NestPathField.java +++ b/solr/core/src/java/org/apache/solr/schema/NestPathField.java @@ -70,7 +70,13 @@ public void setArgs(IndexSchema schema, Map args) { @Override public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) { - if (externalVal == null || externalVal.isEmpty() || "/".equals(externalVal)) { + if (externalVal == null) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Field " + field.getName() + " cannot be queried with a null."); + } + + if (externalVal.isEmpty() || "/".equals(externalVal)) { return new BooleanQuery.Builder() .add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST) .add(field.getType().getExistenceQuery(parser, field), BooleanClause.Occur.MUST_NOT) diff --git a/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java b/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java new file mode 100644 index 000000000000..702c5b6a8ad3 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java @@ -0,0 +1,78 @@ +package org.apache.solr.schema; + +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.search.QParser; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.jupiter.api.Test; + +public class TestNestPathField extends SolrTestCaseJ4 { + + @BeforeClass + public static void beforeClass() throws Exception { + // Initializes the core using a standard test schema that contains a NestPathField + initCore("solrconfig.xml", "schema15.xml"); + } + + @Override + @After + public void tearDown() throws Exception { + clearIndex(); + super.tearDown(); + } + + @Test + public void testGetFieldQueryRootShortcut() throws Exception { + try (SolrQueryRequest req = req()) { + QParser parser = QParser.getParser("*:*", req); + SchemaField field = req.getSchema().getField("_nest_path_"); + NestPathField type = (NestPathField) field.getType(); + + // 1. Verify that a null/omitted value throws a BAD_REQUEST SolrException + SolrException ex = + expectThrows( + SolrException.class, + () -> { + type.getFieldQuery(parser, field, null); + }); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + assertTrue(ex.getMessage().contains("cannot be queried with a null.")); + + // 2. Verify that empty string and root slash trigger the root shortcut query + String[] shortcutInputs = new String[] {"", "/"}; + for (String input : shortcutInputs) { + Query q = type.getFieldQuery(parser, field, input); + + assertTrue( + "Query should be a BooleanQuery for input: '" + input + "'", q instanceof BooleanQuery); + BooleanQuery bq = (BooleanQuery) q; + + assertEquals( + "Root shortcut BooleanQuery must contain exactly 2 clauses", 2, bq.clauses().size()); + + // Assert clause 1: MUST MatchAllDocsQuery + BooleanClause clause1 = bq.clauses().getFirst(); + assertEquals(BooleanClause.Occur.MUST, clause1.occur()); + assertTrue(clause1.query() instanceof MatchAllDocsQuery); + + // Assert clause 2: MUST_NOT ExistenceQuery + BooleanClause clause2 = bq.clauses().get(1); + assertEquals(BooleanClause.Occur.MUST_NOT, clause2.occur()); + assertEquals(type.getExistenceQuery(parser, field), clause2.query()); + } + + // 3. Verify standard paths do not trigger the shortcut and fall back safely + Query standardQ = type.getFieldQuery(parser, field, "/child"); + assertFalse( + "A normal path should not return the root shortcut BooleanQuery", + standardQ instanceof BooleanQuery); + + } + } +} From ba79e8a10a9d24f27fc492716bd2cd654aa918cd Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Mon, 15 Jun 2026 08:58:55 +0530 Subject: [PATCH 4/8] Changed the type as suggested. --- .../apache/solr/schema/TestNestPathField.java | 78 ------------------- .../apache/solr/search/QueryEqualityTest.java | 7 ++ 2 files changed, 7 insertions(+), 78 deletions(-) delete mode 100644 solr/core/src/test/org/apache/solr/schema/TestNestPathField.java diff --git a/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java b/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java deleted file mode 100644 index 702c5b6a8ad3..000000000000 --- a/solr/core/src/test/org/apache/solr/schema/TestNestPathField.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.apache.solr.schema; - -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.search.Query; -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.common.SolrException; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.search.QParser; -import org.junit.After; -import org.junit.BeforeClass; -import org.junit.jupiter.api.Test; - -public class TestNestPathField extends SolrTestCaseJ4 { - - @BeforeClass - public static void beforeClass() throws Exception { - // Initializes the core using a standard test schema that contains a NestPathField - initCore("solrconfig.xml", "schema15.xml"); - } - - @Override - @After - public void tearDown() throws Exception { - clearIndex(); - super.tearDown(); - } - - @Test - public void testGetFieldQueryRootShortcut() throws Exception { - try (SolrQueryRequest req = req()) { - QParser parser = QParser.getParser("*:*", req); - SchemaField field = req.getSchema().getField("_nest_path_"); - NestPathField type = (NestPathField) field.getType(); - - // 1. Verify that a null/omitted value throws a BAD_REQUEST SolrException - SolrException ex = - expectThrows( - SolrException.class, - () -> { - type.getFieldQuery(parser, field, null); - }); - assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); - assertTrue(ex.getMessage().contains("cannot be queried with a null.")); - - // 2. Verify that empty string and root slash trigger the root shortcut query - String[] shortcutInputs = new String[] {"", "/"}; - for (String input : shortcutInputs) { - Query q = type.getFieldQuery(parser, field, input); - - assertTrue( - "Query should be a BooleanQuery for input: '" + input + "'", q instanceof BooleanQuery); - BooleanQuery bq = (BooleanQuery) q; - - assertEquals( - "Root shortcut BooleanQuery must contain exactly 2 clauses", 2, bq.clauses().size()); - - // Assert clause 1: MUST MatchAllDocsQuery - BooleanClause clause1 = bq.clauses().getFirst(); - assertEquals(BooleanClause.Occur.MUST, clause1.occur()); - assertTrue(clause1.query() instanceof MatchAllDocsQuery); - - // Assert clause 2: MUST_NOT ExistenceQuery - BooleanClause clause2 = bq.clauses().get(1); - assertEquals(BooleanClause.Occur.MUST_NOT, clause2.occur()); - assertEquals(type.getExistenceQuery(parser, field), clause2.query()); - } - - // 3. Verify standard paths do not trigger the shortcut and fall back safely - Query standardQ = type.getFieldQuery(parser, field, "/child"); - assertFalse( - "A normal path should not return the root shortcut BooleanQuery", - standardQ instanceof BooleanQuery); - - } - } -} diff --git a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java index ecad83f39de0..500426d64259 100644 --- a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java +++ b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java @@ -2000,6 +2000,13 @@ public void testHashRangeQuery() throws Exception { "{!hash_range l='107347968' u='214695935' f='x_id'}"); } + @Test + public void testNestPathRootShortcut() throws Exception { + try (SolrQueryRequest req = req("df", "_nest_path_")) { + assertQueryEquals(null, req, "{!field f=_nest_path_ v=''}", "{!field f=_nest_path_}/"); + } + } + // Override req to add df param public static SolrQueryRequest req(String... q) { return SolrTestCaseJ4.req(q, "df", "text"); From 26866bc23a5446e0ceac215feccf57d191781106 Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Tue, 16 Jun 2026 10:56:10 +0530 Subject: [PATCH 5/8] Changed the type as suggested. --- .../apache/solr/search/QueryEqualityTest.java | 21 +++++++++++++++++-- .../update/TestNestedUpdateProcessor.java | 4 ++-- .../pages/searching-nested-documents.adoc | 15 +++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java index 500426d64259..3c9bdc39261d 100644 --- a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java +++ b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java @@ -24,6 +24,7 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.FuzzyQuery; +import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.NamedMatches; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermInSetQuery; @@ -697,7 +698,7 @@ public void testBlockJoin() throws Exception { final String parent_path = "/aa/bb"; try (SolrQueryRequest req = req( - "parent_filt", "(*:* -{!prefix f='_nest_path_' v='" + parent_path + "/'})", + "parent_filt", "({!field f='_nest_path_' v='" + parent_path + "/'})", "child_q", "(+foo +{!prefix f='_nest_path_' v='" + parent_path + "/'})", "parent_q", "(+bar +{!field f='_nest_path_' v='" + parent_path + "'})")) { @@ -2003,7 +2004,23 @@ public void testHashRangeQuery() throws Exception { @Test public void testNestPathRootShortcut() throws Exception { try (SolrQueryRequest req = req("df", "_nest_path_")) { - assertQueryEquals(null, req, "{!field f=_nest_path_ v=''}", "{!field f=_nest_path_}/"); + Query parsedQ = + assertQueryEqualsAndReturn( + null, req, "{!field f=_nest_path_ v=''}", "{!field f=_nest_path_}/"); + + var schemaField = req.getSchema().getField("_nest_path_"); + Query expectedQ = + new BooleanQuery.Builder() + .add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST) + .add( + schemaField.getType().getExistenceQuery(null, schemaField), + BooleanClause.Occur.MUST_NOT) + .build(); + + assertEquals( + "The root shortcut query did not form the expected match-all minus existence structure", + expectedQ, + parsedQ); } } diff --git a/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java b/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java index fca48d5765a9..cf43a8df3c88 100644 --- a/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java +++ b/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java @@ -699,7 +699,7 @@ private SolrParams parentQueryMaker(String parent_path, String inner_child_query final String path = parent_path + "/"; return params( "q", "{!parent which=$parent_filt v=$child_q}", - "parent_filt", "(*:* -{!prefix f='_nest_path_' v='" + path + "'})", + "parent_filt", "({!field f='_nest_path_' v='" + path + "'})", "child_q", "(+" + inner_child_query + " +{!prefix f='_nest_path_' v='" + path + "'})"); } else { // '/' has to be escaped otherwise it will be treated as a regex query... @@ -793,7 +793,7 @@ private SolrParams childQueryMaker(String parent_path, String inner_parent_query if (verbose) { return params( "q", "{!child of=$parent_filt v=$parent_q})", - "parent_filt", "(*:* -{!prefix f='_nest_path_' v='" + parent_path + "/'})", + "parent_filt", "({!field f='_nest_path_' v='" + parent_path + "/'})", "parent_q", "(+" + inner_parent_query + " +{!field f='_nest_path_' v='" + parent_path + "'})"); } else { diff --git a/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc b/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc index 81220d51318a..f9618583791d 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc @@ -274,6 +274,21 @@ $ curl 'http://localhost:8983/solr/gettingstarted/select' -d 'omitHeader=true' - }} ---- +=== Root Document Query Shortcut + +When working with hierarchical nested documents, you may frequently need to isolate or filter your search results exclusively to top-level "root" documents (documents that do not belong to a parent path layout). + +Instead of using a verbose negative field existence filter (like `*:* -_nest_path_:*`), the `NestPathField` type supports an explicit root document query shortcut. Passing either a single forward slash (`/`) or an empty string (`''`) automatically constructs a query matching all documents while excluding any nested child paths: + +[source,text] +---- +# Targets only top-level root documents via the slash shortcut +fq={!field f=_nest_path_}/ + +# Alternative equivalent syntax using an empty parameter string +fq={!field f=_nest_path_ v=''} +---- + === Nested Vectors search through Block Join Query Parsers and Child Doc Transformer From a457c03206cd5b9ad80ba7b30aced83bc1b64055 Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Thu, 18 Jun 2026 10:27:57 +0530 Subject: [PATCH 6/8] Changes done as per the review. --- .../org/apache/solr/update/TestNestedUpdateProcessor.java | 2 +- .../modules/query-guide/pages/dense-vector-search.adoc | 6 +++--- .../query-guide/pages/searching-nested-documents.adoc | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java b/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java index cf43a8df3c88..c90a50baa00d 100644 --- a/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java +++ b/solr/core/src/test/org/apache/solr/update/TestNestedUpdateProcessor.java @@ -699,7 +699,7 @@ private SolrParams parentQueryMaker(String parent_path, String inner_child_query final String path = parent_path + "/"; return params( "q", "{!parent which=$parent_filt v=$child_q}", - "parent_filt", "({!field f='_nest_path_' v='" + path + "'})", + "parent_filt", "(*:* -{!prefix f='_nest_path_' v='" + path + "'})", "child_q", "(+" + inner_child_query + " +{!prefix f='_nest_path_' v='" + path + "'})"); } else { // '/' has to be escaped otherwise it will be treated as a regex query... diff --git a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc index 2681cc598409..d5860bc5cf65 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc @@ -537,7 +537,7 @@ Here is an example of a `knn` search using a `childrenOf`: [source,text] ?q={!knn f=vector topK=3 childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents=*:* -_nest_path_:* +&allParents={!field f=_nest_path_ v=''} The search results retrieved are the k=3 nearest documents to the vector in input `[1.0, 2.0, 3.0, 4.0]`, each of them with a different parent. The 'childrenOf' parameter must return all valid parents to guarantee the correct functioning of the query. @@ -558,7 +558,7 @@ Here is an example of a `knn` search using a `parents.preFilter`: [source,text] ?q={!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents=*:* -_nest_path_:* +&allParents={!field f=_nest_path_ v=''} &someParents=color_s:RED The search results retrieved are the k=3 nearest documents to the vector in input `[1.0, 2.0, 3.0, 4.0]`, each of them with a different parent. Only the documents with a parent that satisfy the 'color_s:RED' condition are considered candidates for the ANN search. @@ -603,7 +603,7 @@ So you should query a multivalued vector fields following the same syntax: [source,text] ?q={!parent which=$allParents score=max v=$children.q} &children.q={!knn f=vector_multivalued topK=3 parents.preFilter=$someParents childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents=*:* -_nest_path_:* +&allParents={!field f=_nest_path_ v=''} &someParents=color_s:RED In terms of rendering the results, you need the child transformer if you want to output them flat (you can choose to only return the best vector per result or all vectors): diff --git a/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc b/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc index f9618583791d..879663b17acf 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/searching-nested-documents.adoc @@ -308,7 +308,7 @@ An example: [source,text] ?q={!parent which=$allParents score=max v=$children.q}& children.q={!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0]& -allParents=*:* -_nest_path_:*& +allParents={!field f=_nest_path_ v=''}& someParents=color_s:RED& fl=id,score,vectors,vector,[child fl=vector childFilter=$children.q] From 9da87beb5df2709fab7898d2b66a1827f48999d9 Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Fri, 19 Jun 2026 11:06:00 +0530 Subject: [PATCH 7/8] changes made as per the review to have '/' by default --- .../modules/query-guide/pages/dense-vector-search.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc index d5860bc5cf65..99392789c8fe 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc @@ -537,7 +537,7 @@ Here is an example of a `knn` search using a `childrenOf`: [source,text] ?q={!knn f=vector topK=3 childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents={!field f=_nest_path_ v=''} +&allParents={!field f=_nest_path_ v='/'} The search results retrieved are the k=3 nearest documents to the vector in input `[1.0, 2.0, 3.0, 4.0]`, each of them with a different parent. The 'childrenOf' parameter must return all valid parents to guarantee the correct functioning of the query. @@ -558,7 +558,7 @@ Here is an example of a `knn` search using a `parents.preFilter`: [source,text] ?q={!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents={!field f=_nest_path_ v=''} +&allParents={!field f=_nest_path_ v='/'} &someParents=color_s:RED The search results retrieved are the k=3 nearest documents to the vector in input `[1.0, 2.0, 3.0, 4.0]`, each of them with a different parent. Only the documents with a parent that satisfy the 'color_s:RED' condition are considered candidates for the ANN search. @@ -603,7 +603,7 @@ So you should query a multivalued vector fields following the same syntax: [source,text] ?q={!parent which=$allParents score=max v=$children.q} &children.q={!knn f=vector_multivalued topK=3 parents.preFilter=$someParents childrenOf=$allParents}[1.0, 2.0, 3.0, 4.0] -&allParents={!field f=_nest_path_ v=''} +&allParents={!field f=_nest_path_ v='/'} &someParents=color_s:RED In terms of rendering the results, you need the child transformer if you want to output them flat (you can choose to only return the best vector per result or all vectors): From 35547e9659639ce8696d1d07977b75f84d09d32f Mon Sep 17 00:00:00 2001 From: abumarjikar Date: Sat, 20 Jun 2026 13:29:26 +0530 Subject: [PATCH 8/8] Changes made to use the shortcut for root lny docs search. --- .../org/apache/solr/schema/NestPathField.java | 2 +- .../search/join/BlockJoinParentQParser.java | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/schema/NestPathField.java b/solr/core/src/java/org/apache/solr/schema/NestPathField.java index bf064a4da28e..136e0493e3cb 100644 --- a/solr/core/src/java/org/apache/solr/schema/NestPathField.java +++ b/solr/core/src/java/org/apache/solr/schema/NestPathField.java @@ -78,7 +78,7 @@ public Query getFieldQuery(QParser parser, SchemaField field, String externalVal if (externalVal.isEmpty() || "/".equals(externalVal)) { return new BooleanQuery.Builder() - .add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST) + .add(MatchAllDocsQuery.INSTANCE, BooleanClause.Occur.MUST) .add(field.getType().getExistenceQuery(parser, field), BooleanClause.Occur.MUST_NOT) .build(); } diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinParentQParser.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinParentQParser.java index 6e7dbaf4c63e..4a6176483495 100644 --- a/solr/core/src/java/org/apache/solr/search/join/BlockJoinParentQParser.java +++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinParentQParser.java @@ -171,7 +171,7 @@ protected Query parseUsingParentPath(String parentPath, String childPath) throws if (parsedChildQuery.clauses().isEmpty()) { // i.e. all children // no block-join needed; just return all "parent" docs at this level - return wrapWithParentPathConstraint(parentPath, new MatchAllDocsQuery()); + return wrapWithParentPathConstraint(parentPath, MatchAllDocsQuery.INSTANCE); } // allParents filter: (*:* -{!prefix f="_nest_path_" v="/"}) @@ -209,15 +209,16 @@ protected Query parseUsingParentPath(String parentPath, String childPath) throws * /}): {@code (*:* -_nest_path_:*)} */ protected Query buildAllParentsFilterFromPath(String parentPath) { - final Query excludeQuery; - if (parentPath.equals("/")) { - excludeQuery = newNestPathExistsQuery(); - } else { - excludeQuery = new PrefixQuery(new Term(IndexSchema.NEST_PATH_FIELD_NAME, parentPath + "/")); + if (parentPath.equals("/") || parentPath.isEmpty()) { + final SchemaField nestPathField = req.getSchema().getField(IndexSchema.NEST_PATH_FIELD_NAME); + return nestPathField.getType().getFieldQuery(this, nestPathField, parentPath); } + return new BooleanQuery.Builder() - .add(new MatchAllDocsQuery(), Occur.MUST) - .add(excludeQuery, Occur.MUST_NOT) + .add(MatchAllDocsQuery.INSTANCE, Occur.MUST) + .add( + new PrefixQuery(new Term(IndexSchema.NEST_PATH_FIELD_NAME, parentPath + "/")), + Occur.MUST_NOT) .build(); }