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

yqm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new c4ead9b2301 Allow time series query to return empty preferred ordering 
for All Granularity (#18262)
c4ead9b2301 is described below

commit c4ead9b23010326999c65c566adc601d4b5533c9
Author: Cece Mei <[email protected]>
AuthorDate: Wed Jul 23 09:16:25 2025 -0700

    Allow time series query to return empty preferred ordering for All 
Granularity (#18262)
    
    * allow time series query to return empty preferred ordering for 
`Granularities.ALL`
    
    * minor change in equal
    
    * some updates for docker in IT
    
    * only to cleanup
    
    * some docker image change
    
    * revert IT changes
    
    * some test
    
    * unit test
    
    * more test
    
    * modify test case
---
 .../data/input/impl/AggregateProjectionSpec.java   |   5 +-
 .../query/timeseries/TimeseriesQueryEngine.java    |   4 +-
 .../druid/segment/projections/Projections.java     |  23 ++++-
 .../input/impl/AggregateProjectionSpecTest.java    |  15 +++
 .../query/timeseries/TimeseriesQueryTest.java      |   5 +-
 .../segment/AggregateProjectionMetadataTest.java   |  40 ++++++++
 .../druid/segment/CursorFactoryProjectionTest.java | 101 +++++++++++++++++++++
 7 files changed, 183 insertions(+), 10 deletions(-)

diff --git 
a/processing/src/main/java/org/apache/druid/data/input/impl/AggregateProjectionSpec.java
 
b/processing/src/main/java/org/apache/druid/data/input/impl/AggregateProjectionSpec.java
index 0fd4e614002..925fd26f4fe 100644
--- 
a/processing/src/main/java/org/apache/druid/data/input/impl/AggregateProjectionSpec.java
+++ 
b/processing/src/main/java/org/apache/druid/data/input/impl/AggregateProjectionSpec.java
@@ -32,7 +32,6 @@ import 
org.apache.druid.java.util.common.granularity.Granularity;
 import org.apache.druid.query.OrderBy;
 import org.apache.druid.query.aggregation.AggregatorFactory;
 import org.apache.druid.segment.AggregateProjectionMetadata;
-import org.apache.druid.segment.Cursors;
 import org.apache.druid.segment.VirtualColumn;
 import org.apache.druid.segment.VirtualColumns;
 import org.apache.druid.segment.column.ColumnHolder;
@@ -175,8 +174,8 @@ public class AggregateProjectionSpec
   private static ProjectionOrdering computeOrdering(VirtualColumns 
virtualColumns, List<DimensionSchema> groupingColumns)
   {
     if (groupingColumns.isEmpty()) {
-      // call it time ordered, there is no grouping columns so there is only 1 
row for this projection
-      return new ProjectionOrdering(Cursors.ascendingTimeOrder(), null);
+      // no ordering since there is only 1 row for this projection
+      return new ProjectionOrdering(List.of(), null);
     }
     final List<OrderBy> ordering = 
Lists.newArrayListWithCapacity(groupingColumns.size());
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQueryEngine.java
 
b/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQueryEngine.java
index 45872514b2e..02b9b883501 100644
--- 
a/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQueryEngine.java
+++ 
b/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQueryEngine.java
@@ -337,7 +337,9 @@ public class TimeseriesQueryEngine
                        .setAggregators(query.getAggregatorSpecs())
                        .setQueryContext(query.context())
                        .setPreferredOrdering(
-                           query.isDescending() ? 
Cursors.descendingTimeOrder() : Cursors.ascendingTimeOrder()
+                           query.getGranularity().equals(Granularities.ALL)
+                           ? List.of()
+                           : query.isDescending() ? 
Cursors.descendingTimeOrder() : Cursors.ascendingTimeOrder()
                        )
                        .setQueryMetrics(queryMetrics)
                        .build()
diff --git 
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
 
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
index ce26f322cf4..2a633aadf9d 100644
--- 
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
+++ 
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
@@ -50,6 +50,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.function.Function;
@@ -124,7 +125,7 @@ public class Projections
   /**
    * Returns true if column is defined in {@link 
AggregateProjectionSpec#getGroupingColumns()} OR if the column does not
    * exist in the base table. Part of determining if a projection can be used 
for a given {@link CursorBuildSpec},
-   * 
+   *
    * @see AggregateProjectionMetadata.Schema#matches(CursorBuildSpec, 
PhysicalColumnChecker)
    */
   @FunctionalInterface
@@ -153,6 +154,25 @@ public class Projections
     {
       return remapColumns;
     }
+
+    @Override
+    public boolean equals(Object o)
+    {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof ProjectionMatch)) {
+        return false;
+      }
+      ProjectionMatch that = (ProjectionMatch) o;
+      return Objects.equals(cursorBuildSpec, that.cursorBuildSpec) && 
Objects.equals(remapColumns, that.remapColumns);
+    }
+
+    @Override
+    public int hashCode()
+    {
+      return Objects.hash(cursorBuildSpec, remapColumns);
+    }
   }
 
   public static final class ProjectionMatchBuilder
@@ -208,7 +228,6 @@ public class Projections
     /**
      * Add a query {@link 
AggregatorFactory#substituteCombiningFactory(AggregatorFactory)} which can 
combine the inputs
      * of a selector created by a projection {@link AggregatorFactory}
-     *
      */
     public ProjectionMatchBuilder addPreAggregatedAggregator(AggregatorFactory 
aggregator)
     {
diff --git 
a/processing/src/test/java/org/apache/druid/data/input/impl/AggregateProjectionSpecTest.java
 
b/processing/src/test/java/org/apache/druid/data/input/impl/AggregateProjectionSpecTest.java
index 2e15cdec490..dcb47416321 100644
--- 
a/processing/src/test/java/org/apache/druid/data/input/impl/AggregateProjectionSpecTest.java
+++ 
b/processing/src/test/java/org/apache/druid/data/input/impl/AggregateProjectionSpecTest.java
@@ -64,6 +64,21 @@ class AggregateProjectionSpecTest extends 
InitializedNullHandlingTest
     Assertions.assertEquals(spec, 
JSON_MAPPER.readValue(JSON_MAPPER.writeValueAsString(spec), 
AggregateProjectionSpec.class));
   }
 
+  @Test
+  void testComputeOrdering_noOrdering()
+  {
+    AggregateProjectionSpec spec = new AggregateProjectionSpec(
+        "some_projection",
+        VirtualColumns.EMPTY,
+        List.of(),
+        new AggregatorFactory[] {
+            new CountAggregatorFactory("count"),
+            new LongSumAggregatorFactory("e", "e")
+        }
+    );
+    Assertions.assertTrue(spec.getOrdering().isEmpty());
+  }
+
   @Test
   void testMissingName()
   {
diff --git 
a/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryTest.java
 
b/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryTest.java
index 9834e221cd5..2565a7467e2 100644
--- 
a/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryTest.java
@@ -150,10 +150,7 @@ public class TimeseriesQueryTest extends 
InitializedNullHandlingTest
         buildSpec.getAggregators()
     );
     Assert.assertEquals(virtualColumns, buildSpec.getVirtualColumns());
-    Assert.assertEquals(
-        descending ? Cursors.descendingTimeOrder() : 
Cursors.ascendingTimeOrder(),
-        buildSpec.getPreferredOrdering()
-    );
+    Assert.assertTrue(buildSpec.getPreferredOrdering().isEmpty());
   }
 
   @Test
diff --git 
a/processing/src/test/java/org/apache/druid/segment/AggregateProjectionMetadataTest.java
 
b/processing/src/test/java/org/apache/druid/segment/AggregateProjectionMetadataTest.java
index 1c7d1bfe24c..8e025600686 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/AggregateProjectionMetadataTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/AggregateProjectionMetadataTest.java
@@ -21,6 +21,9 @@ package org.apache.druid.segment;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet;
 import nl.jqno.equalsverifier.EqualsVerifier;
 import org.apache.druid.error.DruidException;
@@ -30,6 +33,7 @@ import org.apache.druid.query.aggregation.AggregatorFactory;
 import org.apache.druid.query.aggregation.CountAggregatorFactory;
 import org.apache.druid.query.aggregation.LongSumAggregatorFactory;
 import org.apache.druid.segment.column.ColumnHolder;
+import org.apache.druid.segment.projections.Projections;
 import org.apache.druid.testing.InitializedNullHandlingTest;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -293,4 +297,40 @@ class AggregateProjectionMetadataTest extends 
InitializedNullHandlingTest
                   .usingGetClass()
                   .verify();
   }
+
+  @Test
+  public void testSchemaMatchSimple()
+  {
+    // arrange
+    AggregateProjectionMetadata spec = new AggregateProjectionMetadata(
+        new AggregateProjectionMetadata.Schema(
+            "some_projection",
+            null,
+            VirtualColumns.EMPTY,
+            Arrays.asList("a", "b"),
+            new AggregatorFactory[]{new 
LongSumAggregatorFactory("a_projection", "a")},
+            Arrays.asList(OrderBy.ascending("a"), OrderBy.ascending("b"))
+        ),
+        12345
+    );
+    CursorBuildSpec cursorBuildSpec = CursorBuildSpec.builder()
+                                                     
.setPreferredOrdering(ImmutableList.of())
+                                                     
.setAggregators(ImmutableList.of(new LongSumAggregatorFactory(
+                                                         "a",
+                                                         "a"
+                                                     )))
+                                                     .build();
+    // act & assert
+    Projections.ProjectionMatch projectionMatch = spec.getSchema()
+                                                      
.matches(cursorBuildSpec, (projectionName, columnName) -> true);
+    Projections.ProjectionMatch expected = new Projections.ProjectionMatch(
+        CursorBuildSpec.builder()
+                       .setAggregators(ImmutableList.of(new 
LongSumAggregatorFactory("a", "a")))
+                       .setPhysicalColumns(ImmutableSet.of("a_projection"))
+                       .setPreferredOrdering(ImmutableList.of())
+                       .build(),
+        ImmutableMap.of("a", "a_projection")
+    );
+    Assertions.assertEquals(expected, projectionMatch);
+  }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/segment/CursorFactoryProjectionTest.java
 
b/processing/src/test/java/org/apache/druid/segment/CursorFactoryProjectionTest.java
index 7936b06329c..32f695b76b1 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/CursorFactoryProjectionTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/CursorFactoryProjectionTest.java
@@ -226,6 +226,14 @@ public class CursorFactoryProjectionTest extends 
InitializedNullHandlingTest
               new LongSumAggregatorFactory("_c_sum", "c")
           }
       ),
+      new AggregateProjectionSpec(
+          "b_c_sum",
+          VirtualColumns.EMPTY,
+          List.of(new StringDimensionSchema("b")),
+          new AggregatorFactory[]{
+              new LongSumAggregatorFactory("_c_sum", "c")
+          }
+      ),
       new AggregateProjectionSpec(
           "ab",
           null,
@@ -1343,6 +1351,99 @@ public class CursorFactoryProjectionTest extends 
InitializedNullHandlingTest
     Assert.assertArrayEquals(new Object[]{"b", null, 12L, 13.2}, 
results.get(1).getArray());
   }
 
+  @Test
+  public void testTimeseriesQueryAllGranularityCanMatchNonTimeDimProjection()
+  {
+    final TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
+                                        .dataSource("test")
+                                        
.intervals(ImmutableList.of(Intervals.ETERNITY))
+                                        .granularity(Granularities.ALL)
+                                        .aggregators(new 
LongSumAggregatorFactory("c_sum", "c"))
+                                        
.context(ImmutableMap.of(QueryContexts.USE_PROJECTION, "b_c_sum"))
+                                        .build();
+
+    final CursorBuildSpec buildSpec = 
TimeseriesQueryEngine.makeCursorBuildSpec(query, null);
+    try (final CursorHolder cursorHolder = 
projectionsCursorFactory.makeCursorHolder(buildSpec)) {
+      final Cursor cursor = cursorHolder.asCursor();
+      int rowCount = 0;
+      while (!cursor.isDone()) {
+        rowCount++;
+        cursor.advance();
+      }
+      Assert.assertEquals(4, rowCount);
+    }
+
+    final Sequence<Result<TimeseriesResultValue>> resultRows = 
timeseriesEngine.process(
+        query,
+        projectionsCursorFactory,
+        projectionsTimeBoundaryInspector,
+        null
+    );
+
+    final List<Result<TimeseriesResultValue>> results = resultRows.toList();
+    Assert.assertEquals(1, results.size());
+    final RowSignature querySignature = 
query.getResultRowSignature(RowSignature.Finalization.YES);
+    Assert.assertArrayEquals(new Object[]{TIMESTAMP, 19L}, 
getResultArray(results.get(0), querySignature));
+  }
+
+  @Test
+  public void testTimeseriesQueryAllGranularitiesAlwaysRuns()
+  {
+    // Same test as 
testTimeseriesQueryAllGranularityCanMatchNonTimeDimProjection, but no 
projection used.
+    // Query can run with segment time ordering on/off.
+    final TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
+                                        .dataSource("test")
+                                        
.intervals(ImmutableList.of(Intervals.ETERNITY))
+                                        .granularity(Granularities.ALL)
+                                        .aggregators(new 
LongSumAggregatorFactory("c_sum", "c"))
+                                        
.context(ImmutableMap.of(QueryContexts.NO_PROJECTIONS, true))
+                                        .build();
+
+    final CursorBuildSpec buildSpec = 
TimeseriesQueryEngine.makeCursorBuildSpec(query, null);
+    try (final CursorHolder cursorHolder = 
projectionsCursorFactory.makeCursorHolder(buildSpec)) {
+      final Cursor cursor = cursorHolder.asCursor();
+      int rowCount = 0;
+      while (!cursor.isDone()) {
+        rowCount++;
+        cursor.advance();
+      }
+      Assert.assertEquals(8, rowCount);
+    }
+
+    final Sequence<Result<TimeseriesResultValue>> resultRows = 
timeseriesEngine.process(
+        query,
+        projectionsCursorFactory,
+        projectionsTimeBoundaryInspector,
+        null
+    );
+
+    final List<Result<TimeseriesResultValue>> results = resultRows.toList();
+    Assert.assertEquals(1, results.size());
+    final RowSignature querySignature = 
query.getResultRowSignature(RowSignature.Finalization.YES);
+    Assert.assertArrayEquals(new Object[]{TIMESTAMP, 19L}, 
getResultArray(results.get(0), querySignature));
+  }
+
+  @Test
+  public void testTimeseriesQueryOrderByNotCompatibleWithProjection()
+  {
+    // Query has `__time ASC` ordering, but projection has `b ASC` ordering, 
so no projection can be used.
+    // This is not ideal, since we know query has Granularities.DAY, and 
segment also has Granularities.DAY, so the __time ordering does not matter.
+    final TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
+                                        .dataSource("test")
+                                        
.intervals(ImmutableList.of(Intervals.ETERNITY))
+                                        .granularity(Granularities.DAY)
+                                        .aggregators(new 
LongSumAggregatorFactory("c_sum", "c"))
+                                        
.context(ImmutableMap.of(QueryContexts.USE_PROJECTION, "b_c_sum"))
+                                        .build();
+
+    final CursorBuildSpec buildSpec = 
TimeseriesQueryEngine.makeCursorBuildSpec(query, null);
+    DruidException e = Assert.assertThrows(
+        DruidException.class,
+        () -> projectionsCursorFactory.makeCursorHolder(buildSpec));
+    Assert.assertEquals(DruidException.Category.INVALID_INPUT, 
e.getCategory());
+    Assert.assertEquals("Projection[b_c_sum] specified, but does not satisfy 
query", e.getMessage());
+  }
+
   @Test
   public void testTimeseriesQueryGranularityFitsProjectionGranularity()
   {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to