You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2026/04/26 04:19:18 UTC

(solr) branch branch_9x updated: SOLR-18039: Support repeated parents.preFilter in KnnQParser (#4139)

This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 4a2a26c6055 SOLR-18039: Support repeated parents.preFilter in KnnQParser (#4139)
4a2a26c6055 is described below

commit 4a2a26c605584dabfd0afafa60ff1aa49b51a595
Author: Arup Chauhan <ar...@gmail.com>
AuthorDate: Thu Feb 19 07:54:59 2026 -0600

    SOLR-18039: Support repeated parents.preFilter in KnnQParser (#4139)
    
    Signed-off-by: Arup Chauhan <ar...@gmail.com>
---
 .../unreleased/parents-prefilter-multi-value.yml   |   9 +
 .../join/BlockJoinNestedVectorsQParserTest.java    | 454 +++++++++++++++++++++
 2 files changed, 463 insertions(+)

diff --git a/changelog/unreleased/parents-prefilter-multi-value.yml b/changelog/unreleased/parents-prefilter-multi-value.yml
new file mode 100644
index 00000000000..15e6615aa4e
--- /dev/null
+++ b/changelog/unreleased/parents-prefilter-multi-value.yml
@@ -0,0 +1,9 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: Support repeated parents.preFilter values in nested vector knn queries
+type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other
+authors:
+  - name: Arup Chauhan
+    nick: arup-chauhan
+links:
+  - name: SOLR-18039
+    url: https://issues.apache.org/jira/browse/SOLR-18039
diff --git a/solr/core/src/test/org/apache/solr/search/join/BlockJoinNestedVectorsQParserTest.java b/solr/core/src/test/org/apache/solr/search/join/BlockJoinNestedVectorsQParserTest.java
new file mode 100644
index 00000000000..176aaf224c1
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/join/BlockJoinNestedVectorsQParserTest.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.search.join;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.util.RandomNoReverseMergePolicyFactory;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+public class BlockJoinNestedVectorsQParserTest extends SolrTestCaseJ4 {
+  private static final List<Float> FLOAT_QUERY_VECTOR = Arrays.asList(1.0f, 1.0f, 1.0f, 1.0f);
+  private static final List<Integer> BYTE_QUERY_VECTOR = Arrays.asList(1, 1, 1, 1);
+
+  @ClassRule
+  public static final TestRule noReverseMerge = RandomNoReverseMergePolicyFactory.createRule();
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml", "schema15.xml");
+    prepareIndex();
+  }
+
+  public static void prepareIndex() throws Exception {
+    List<SolrInputDocument> docsToIndex = prepareDocs();
+    for (SolrInputDocument doc : docsToIndex) {
+      assertU(adoc(doc));
+    }
+    assertU(commit());
+  }
+
+  /**
+   * The documents in the index are 10 parents, with some parent level metadata and 30 nested
+   * documents (with vectors and children level metadata) Each parent document has 3 nested
+   * documents with vectors.
+   *
+   * <p>This allows to run knn queries both at parent/children level and using various pre-filters
+   * both for parent metadata and children.
+   *
+   * @return a list of documents to index
+   */
+  private static List<SolrInputDocument> prepareDocs() {
+    int totalParentDocuments = 10;
+    int totalNestedVectors = 30;
+    int perParentChildren = totalNestedVectors / totalParentDocuments;
+
+    final String[] klm = new String[] {"k", "l", "m"};
+    final String[] abcdef = new String[] {"a", "b", "c", "d", "e", "f"};
+
+    List<SolrInputDocument> docs = new ArrayList<>(totalParentDocuments);
+    for (int i = 1; i < totalParentDocuments + 1; i++) {
+      SolrInputDocument doc = new SolrInputDocument();
+      doc.setField("id", i);
+      doc.setField("parent_b", true);
+
+      doc.setField("parent_s", abcdef[i % abcdef.length]);
+      List<SolrInputDocument> children = new ArrayList<>(perParentChildren);
+
+      // nested vector documents have a distance from the query vector inversely proportional to
+      // their id
+      for (int j = 0; j < perParentChildren; j++) {
+        SolrInputDocument child = new SolrInputDocument();
+        child.setField("id", i + "" + j);
+        child.setField("child_s", klm[i % klm.length]);
+        child.setField("vector", outDistanceFloat(FLOAT_QUERY_VECTOR, totalNestedVectors));
+        child.setField("vector_byte", outDistanceByte(BYTE_QUERY_VECTOR, totalNestedVectors));
+        totalNestedVectors--; // the higher the id of the nested document, lower the distance with
+        // the query vector
+        children.add(child);
+      }
+      doc.setField("vectors", children);
+      docs.add(doc);
+    }
+
+    return docs;
+  }
+
+  /**
+   * Generate a resulting float vector with a distance from the original vector that is proportional
+   * to the value in input (higher the value, higher the distance from the original vector)
+   *
+   * @param vector a numerical vector
+   * @param value a numerical value to be added to the first element of the vector
+   * @return a numerical vector that has a distance from the input vector, proportional to the value
+   */
+  private static List<Float> outDistanceFloat(List<Float> vector, int value) {
+    List<Float> result = new ArrayList<>(vector.size());
+    for (int i = 0; i < vector.size(); i++) {
+      if (i == 0) {
+        result.add(vector.get(i) + value);
+      } else {
+        result.add(vector.get(i));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Generate a resulting byte vector with a distance from the original vector that is proportional
+   * to the value in input (higher the value, higher the distance from the original vector)
+   *
+   * @param vector a numerical vector
+   * @param value a numerical value to be added to the first element of the vector
+   * @return a numerical vector that has a distance from the input vector, proportional to the value
+   */
+  private static List<Integer> outDistanceByte(List<Integer> vector, int value) {
+    List<Integer> result = new ArrayList<>(vector.size());
+    for (int i = 0; i < vector.size(); i++) {
+      if (i == 0) {
+        result.add(vector.get(i) + value);
+      } else {
+        result.add(vector.get(i));
+      }
+    }
+    return result;
+  }
+
+  @Test
+  public void parentRetrieval_knnChildrenDiversifyingWithNoAllParents_shouldThrowException() {
+    assertQEx(
+        "When running a diversifying children KNN query, 'childrenOf' parameter is required",
+        req(
+            "q",
+            "{!parent which=$allParents score=max v=$children.q}",
+            "fl",
+            "id,score",
+            "children.q",
+            "{!knn f=vector topK=3 parents.preFilter=$someParents}" + FLOAT_QUERY_VECTOR,
+            "allParents",
+            "parent_s:[* TO *]",
+            "someParents",
+            "parent_s:(a c)"),
+        400);
+  }
+
+  @Test
+  public void childrenRetrievalFloat_filteringByParentMetadata_shouldReturnKnnChildren() {
+    assertQ(
+        req(
+            "fq", "{!child of=$allParents filters=$parent.fq}",
+            "q", "{!knn f=vector topK=5}" + FLOAT_QUERY_VECTOR,
+            "fl", "id",
+            "parent.fq", "parent_s:(a c)",
+            "allParents", "parent_s:[* TO *]"),
+        "//*[@numFound='5']",
+        "//result/doc[1]/str[@name='id'][.='82']",
+        "//result/doc[2]/str[@name='id'][.='81']",
+        "//result/doc[3]/str[@name='id'][.='80']",
+        "//result/doc[4]/str[@name='id'][.='62']",
+        "//result/doc[5]/str[@name='id'][.='61']");
+  }
+
+  @Test
+  public void childrenRetrievalByte_filteringByParentMetadata_shouldReturnKnnChildren() {
+    assertQ(
+        req(
+            "fq", "{!child of=$allParents filters=$parent.fq}",
+            "q", "{!knn f=vector_byte topK=5}" + BYTE_QUERY_VECTOR,
+            "fl", "id",
+            "parent.fq", "parent_s:(a c)",
+            "allParents", "parent_s:[* TO *]"),
+        "//*[@numFound='5']",
+        "//result/doc[1]/str[@name='id'][.='82']",
+        "//result/doc[2]/str[@name='id'][.='81']",
+        "//result/doc[3]/str[@name='id'][.='80']",
+        "//result/doc[4]/str[@name='id'][.='62']",
+        "//result/doc[5]/str[@name='id'][.='61']");
+  }
+
+  @Test
+  public void parentRetrievalFloat_knnChildren_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q", "{!knn f=vector topK=3 childrenOf=$allParents}" + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]"),
+        "//*[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='10']",
+        "//result/doc[2]/str[@name='id'][.='9']",
+        "//result/doc[3]/str[@name='id'][.='8']");
+  }
+
+  @Test
+  public void parentRetrievalFloat_knnChildrenWithNoDiversifying_shouldReturnOneParent() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q", "{!knn f=vector topK=3}" + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]"),
+        "//*[@numFound='1']",
+        "//result/doc[1]/str[@name='id'][.='10']");
+  }
+
+  @Test
+  public void parentRetrievalFloat_knnChildrenWithParentFilter_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q",
+                "{!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(a c)"),
+        "//*[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[2]/str[@name='id'][.='6']",
+        "//result/doc[3]/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void parentRetrievalFloat_knnChildrenWithMultipleParentFilters_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q",
+                "{!knn f=vector topK=3 parents.preFilter=$parentFilter1 parents.preFilter=$parentFilter2 childrenOf=$allParents}"
+                    + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "parentFilter1", "parent_s:(a c)",
+            "parentFilter2", "parent_s:(c e)"),
+        "//*[@numFound='2']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[2]/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void
+      parentRetrievalFloat_knnChildrenWithParentFilterAndChildrenFilter_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q",
+                "{!knn f=vector topK=3 preFilter=child_s:m parents.preFilter=$someParents childrenOf=$allParents}"
+                    + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(a c)"),
+        "//*[@numFound='2']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[2]/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void parentRetrievalByte_knnChildren_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q", "{!knn f=vector_byte topK=3 childrenOf=$allParents}" + BYTE_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]"),
+        "//*[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='10']",
+        "//result/doc[2]/str[@name='id'][.='9']",
+        "//result/doc[3]/str[@name='id'][.='8']");
+  }
+
+  @Test
+  public void parentRetrievalByte_knnChildrenWithParentFilter_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q",
+                "{!knn f=vector_byte topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + BYTE_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(a c)"),
+        "//*[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[2]/str[@name='id'][.='6']",
+        "//result/doc[3]/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void
+      parentRetrievalByte_knnChildrenWithParentFilterAndChildrenFilter_shouldReturnKnnParents() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score",
+            "children.q",
+                "{!knn f=vector_byte topK=3 preFilter=child_s:m parents.preFilter=$someParents childrenOf=$allParents}"
+                    + BYTE_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(a c)"),
+        "//*[@numFound='2']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[2]/str[@name='id'][.='2']");
+  }
+
+  @Test
+  public void
+      parentRetrievalFloat_topKWithChildTransformerWithFilter_shouldUseOriginalChildTransformerFilter() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score,vectors,vector,[child limit=2 fl=vector]",
+            "children.q",
+                "{!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(a c)"),
+        "//result[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='10.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[1][.='9.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[2]/str[@name='id'][.='6']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='16.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[1][.='15.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[3]/str[@name='id'][.='2']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='28.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[1][.='27.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector']/float[4][.='1.0']");
+  }
+
+  @Test
+  public void parentRetrievalFloat_topKWithChildTransformerWithFilter_shouldReturnBestChild() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score,vectors,vector,[child fl=vector childFilter=$children.q]",
+            "children.q",
+                "{!knn f=vector topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + FLOAT_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(b c)"),
+        "//result[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='8.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[2]/str[@name='id'][.='7']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='11.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']",
+        "//result/doc[3]/str[@name='id'][.='2']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[1][.='26.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[2][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[3][.='1.0']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector']/float[4][.='1.0']");
+  }
+
+  @Test
+  public void parentRetrievalByte_topKWithChildTransformer_shouldReturnAllChildren() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score,vectors,vector_byte,[child limit=2 fl=vector_byte]",
+            "children.q",
+                "{!knn f=vector_byte topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + BYTE_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(b c)"),
+        "//result[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='10']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[1][.='9']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[2]/str[@name='id'][.='7']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='13']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[1][.='12']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[3]/str[@name='id'][.='2']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='28']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[1][.='27']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[2]/arr[@name='vector_byte']/int[4][.='1']");
+  }
+
+  @Test
+  public void parentRetrievalByte_topKWithChildTransformerWithFilter_shouldReturnBestChild() {
+    assertQ(
+        req(
+            "q", "{!parent which=$allParents score=max v=$children.q}",
+            "fl", "id,score,vectors,vector_byte,[child fl=vector_byte childFilter=$children.q]",
+            "children.q",
+                "{!knn f=vector_byte topK=3 parents.preFilter=$someParents childrenOf=$allParents}"
+                    + BYTE_QUERY_VECTOR,
+            "allParents", "parent_s:[* TO *]",
+            "someParents", "parent_s:(b c)"),
+        "//result[@numFound='3']",
+        "//result/doc[1]/str[@name='id'][.='8']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='8']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[1]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[2]/str[@name='id'][.='7']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='11']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[2]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']",
+        "//result/doc[3]/str[@name='id'][.='2']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[1][.='26']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[2][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[3][.='1']",
+        "//result/doc[3]/arr[@name='vectors'][1]/doc[1]/arr[@name='vector_byte']/int[4][.='1']");
+  }
+}