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

joerghoh pushed a commit to branch SLING-13248
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git

commit ca6560abd58681cb9f44dce8816fd6ef4555b5c6
Author: Joerg Hoh <[email protected]>
AuthorDate: Tue Jul 21 12:42:27 2026 +0200

    SLING-13248 - Fix false-positive ordering check for multivalued properties 
in PagedQueryIterator
---
 .../impl/mapping/PagedQueryIterator.java           |  8 +++++-
 .../impl/mapping/PagedQueryIteratorTest.java       | 32 ++++++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIterator.java
 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIterator.java
index 40356f06..fc533144 100644
--- 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIterator.java
+++ 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIterator.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
@@ -88,7 +90,11 @@ public class PagedQueryIterator implements 
Iterator<Resource> {
         final String[] values = resource.getValueMap().get(propertyName, 
defaultValue);
 
         if (values.length > 0) {
-            String value = values[0];
+            // The query orders by FIRST([propertyName]), i.e. the smallest 
value of a (possibly
+            // multivalued) property. The ValueMap, however, returns the 
values in storage order, so
+            // values[0] is not necessarily the smallest one. Use the minimum 
to match the query's
+            // sort key; otherwise the ordering checks below raise false 
positives (SLING-13266).
+            String value = 
Arrays.stream(values).min(Comparator.naturalOrder()).orElse(values[0]);
             if (value.compareTo(lastKey) < 0) {
                 String message = String.format(
                         "unexpected query result in page %d, property name 
'%s', got '%s', despite querying for > '%s'",
diff --git 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIteratorTest.java
 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIteratorTest.java
index 61e1cc5e..5741ea21 100644
--- 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIteratorTest.java
+++ 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PagedQueryIteratorTest.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -170,6 +171,37 @@ public class PagedQueryIteratorTest extends 
AbstractMappingMapEntriesTest {
         checkResult(it, expected);
     }
 
+    /**
+     * A multivalued property is returned by the query in the order of its 
smallest value
+     * ({@code FIRST([prop])}), but the ValueMap exposes the values in storage 
order. The iterator
+     * must therefore compare on the smallest value, not on {@code values[0]}: 
when index 0 is not
+     * the smallest value, the two orderings diverge and comparing on {@code 
values[0]} would raise
+     * a spurious {@link PagedQueryIterator.QueryImplementationException}.
+     */
+    @Test
+    public void testMultiValuedFirstNotAtIndexZero() {
+        // resources are returned in FIRST()/minimum order: "/aaa" < 
"/acs-commons"
+        Resource r1 = multiValuedResource("2025-10-14T20:32:01.481Z", "/aaa");
+        Resource r2 = multiValuedResource("/acs-commons");
+        Collection<Resource> resources = Arrays.asList(r1, r2);
+        when(resourceResolver.findResources("multivalued ''", 
"JCR-SQL2")).thenReturn(resources.iterator());
+        PagedQueryIterator it = new PagedQueryIterator("alias", PROPNAME, 
resourceResolver, "multivalued '%s'", 2000);
+
+        List<Resource> result = new ArrayList<>();
+        while (it.hasNext()) {
+            result.add(it.next());
+        }
+        assertEquals(Arrays.asList(r1, r2), result);
+    }
+
+    private static Resource multiValuedResource(String... values) {
+        ValueMap m = mock(ValueMap.class);
+        when(m.get(eq(PROPNAME), any(Object.class))).thenReturn(values);
+        Resource r = mock(Resource.class);
+        when(r.getValueMap()).thenReturn(m);
+        return r;
+    }
+
     private static Collection<Resource> toResourceList(String... keys) {
         Collection<Resource> result = new ArrayList<>();
         for (String key : keys) {

Reply via email to