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

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


The following commit(s) were added to refs/heads/24.0.0 by this push:
     new 1e16a3cc9f add json function support for paths with negative array 
indexes (#12972) (#12979)
1e16a3cc9f is described below

commit 1e16a3cc9f21adcd74777e35b4c545a1445a7ecb
Author: Clint Wylie <[email protected]>
AuthorDate: Fri Aug 26 17:39:15 2022 -0700

    add json function support for paths with negative array indexes (#12972) 
(#12979)
---
 .../segment/nested/NestedPathArrayElement.java     |  7 +-
 .../segment/virtual/NestedFieldVirtualColumn.java  | 78 +++++++++++++------
 .../druid/segment/nested/NestedPathFinderTest.java | 24 ++++++
 .../virtual/NestedFieldVirtualColumnTest.java      |  1 +
 .../sql/calcite/CalciteNestedDataQueryTest.java    | 91 ++++++++++++++++++++++
 5 files changed, 177 insertions(+), 24 deletions(-)

diff --git 
a/processing/src/main/java/org/apache/druid/segment/nested/NestedPathArrayElement.java
 
b/processing/src/main/java/org/apache/druid/segment/nested/NestedPathArrayElement.java
index 095976735d..5bba77fbc9 100644
--- 
a/processing/src/main/java/org/apache/druid/segment/nested/NestedPathArrayElement.java
+++ 
b/processing/src/main/java/org/apache/druid/segment/nested/NestedPathArrayElement.java
@@ -44,7 +44,12 @@ public class NestedPathArrayElement implements NestedPathPart
     // handle lists or arrays because who knows what might end up here, 
depending on how is created
     if (input instanceof List) {
       List<?> currentList = (List<?>) input;
-      if (currentList.size() > index) {
+      final int currentSize = currentList.size();
+      if (index < 0) {
+        if (currentSize + index >= 0) {
+          return currentList.get(currentSize + index);
+        }
+      } else if (currentList.size() > index) {
         return currentList.get(index);
       }
     } else if (input instanceof Object[]) {
diff --git 
a/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
 
b/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
index e972bb95e3..df06eb44f3 100644
--- 
a/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
+++ 
b/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
@@ -45,6 +45,7 @@ import org.apache.druid.segment.column.ValueType;
 import org.apache.druid.segment.data.ReadableOffset;
 import org.apache.druid.segment.nested.NestedDataComplexColumn;
 import org.apache.druid.segment.nested.NestedDataComplexTypeSerde;
+import org.apache.druid.segment.nested.NestedPathArrayElement;
 import org.apache.druid.segment.nested.NestedPathFinder;
 import org.apache.druid.segment.nested.NestedPathPart;
 import org.apache.druid.segment.nested.StructuredData;
@@ -89,6 +90,8 @@ public class NestedFieldVirtualColumn implements VirtualColumn
   private final List<NestedPathPart> parts;
   private final boolean processFromRaw;
 
+  private final boolean hasNegativeArrayIndex;
+
   @JsonCreator
   public NestedFieldVirtualColumn(
       @JsonProperty("columnName") String columnName,
@@ -114,6 +117,17 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
       boolean isInputJq = useJqSyntax != null && useJqSyntax;
       this.parts = isInputJq ? NestedPathFinder.parseJqPath(path) : 
NestedPathFinder.parseJsonPath(path);
     }
+    boolean hasNegative = false;
+    for (NestedPathPart part : this.parts) {
+      if (part instanceof NestedPathArrayElement) {
+        NestedPathArrayElement elementPart = (NestedPathArrayElement) part;
+        if (elementPart.getIndex() < 0) {
+          hasNegative = true;
+          break;
+        }
+      }
+    }
+    this.hasNegativeArrayIndex = hasNegative;
     this.expectedType = expectedType;
     this.processFromRaw = processFromRaw == null ? false : processFromRaw;
   }
@@ -192,27 +206,7 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
     // written to segment, so we fall back to processing the structured data 
from a column value selector on the
     // complex column
     ColumnValueSelector valueSelector = 
makeColumnValueSelector(dimensionSpec.getOutputName(), factory);
-
-    class FieldDimensionSelector extends BaseSingleValueDimensionSelector
-    {
-      @Override
-      public void inspectRuntimeShape(RuntimeShapeInspector inspector)
-      {
-        inspector.visit("valueSelector", valueSelector);
-      }
-
-      @Nullable
-      @Override
-      protected String getValue()
-      {
-        Object val = valueSelector.getObject();
-        if (val == null || val instanceof String) {
-          return (String) val;
-        }
-        return String.valueOf(val);
-      }
-    }
-    return new FieldDimensionSelector();
+    return new FieldDimensionSelector(valueSelector);
   }
 
   @Override
@@ -244,6 +238,14 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
       // complex column itself didn't exist
       return DimensionSelector.constant(null);
     }
+    if (hasNegativeArrayIndex) {
+      return new FieldDimensionSelector(
+          new RawFieldLiteralColumnValueSelector(
+              column.makeColumnValueSelector(offset),
+              parts
+          )
+      );
+    }
     return column.makeDimensionSelector(parts, offset, 
dimensionSpec.getExtractionFn());
   }
 
@@ -265,13 +267,15 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
     // is JSON_VALUE which only returns literals, so we can use the nested 
columns value selector
     return processFromRaw
            ? new 
RawFieldColumnSelector(column.makeColumnValueSelector(offset), parts)
-           : column.makeColumnValueSelector(parts, offset);
+           : hasNegativeArrayIndex
+             ? new 
RawFieldLiteralColumnValueSelector(column.makeColumnValueSelector(offset), 
parts)
+             : column.makeColumnValueSelector(parts, offset);
   }
 
   @Override
   public boolean canVectorize(ColumnInspector inspector)
   {
-    return true;
+    return !hasNegativeArrayIndex;
   }
 
   @Nullable
@@ -286,6 +290,7 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
     if (column == null) {
       return NilVectorSelector.create(offset);
     }
+
     return column.makeSingleValueDimensionVectorSelector(parts, offset);
   }
 
@@ -748,4 +753,31 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
       return StructuredData.wrap(NestedPathFinder.find(data == null ? null : 
data.getValue(), parts));
     }
   }
+
+  public static class FieldDimensionSelector extends 
BaseSingleValueDimensionSelector
+  {
+    private final ColumnValueSelector valueSelector;
+
+    public FieldDimensionSelector(ColumnValueSelector valueSelector)
+    {
+      this.valueSelector = valueSelector;
+    }
+
+    @Override
+    public void inspectRuntimeShape(RuntimeShapeInspector inspector)
+    {
+      inspector.visit("valueSelector", valueSelector);
+    }
+
+    @Nullable
+    @Override
+    protected String getValue()
+    {
+      Object val = valueSelector.getObject();
+      if (val == null || val instanceof String) {
+        return (String) val;
+      }
+      return String.valueOf(val);
+    }
+  }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/segment/nested/NestedPathFinderTest.java
 
b/processing/src/test/java/org/apache/druid/segment/nested/NestedPathFinderTest.java
index b60a30c8a6..161a752ce1 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/nested/NestedPathFinderTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/nested/NestedPathFinderTest.java
@@ -228,6 +228,18 @@ public class NestedPathFinderTest
     Assert.assertEquals(".\"x\"[1]", 
NestedPathFinder.toNormalizedJqPath(pathParts));
     Assert.assertEquals("$.x[1]", 
NestedPathFinder.toNormalizedJsonPath(pathParts));
 
+
+    // { "x" : ["a", "b"]}
+    pathParts = NestedPathFinder.parseJsonPath("$.x[-1]");
+    Assert.assertEquals(2, pathParts.size());
+
+    Assert.assertTrue(pathParts.get(0) instanceof NestedPathField);
+    Assert.assertEquals("x", pathParts.get(0).getPartIdentifier());
+    Assert.assertTrue(pathParts.get(1) instanceof NestedPathArrayElement);
+    Assert.assertEquals("-1", pathParts.get(1).getPartIdentifier());
+    Assert.assertEquals(".\"x\"[-1]", 
NestedPathFinder.toNormalizedJqPath(pathParts));
+    Assert.assertEquals("$.x[-1]", 
NestedPathFinder.toNormalizedJsonPath(pathParts));
+
     // { "x" : ["a", "b"]}
     pathParts = NestedPathFinder.parseJsonPath("$['x'][1]");
     Assert.assertEquals(2, pathParts.size());
@@ -422,6 +434,18 @@ public class NestedPathFinderTest
     Assert.assertEquals("b", NestedPathFinder.find(NESTER, pathParts));
     Assert.assertEquals("b", NestedPathFinder.findStringLiteral(NESTER, 
pathParts));
 
+    pathParts = NestedPathFinder.parseJqPath(".x[-1]");
+    Assert.assertEquals("c", NestedPathFinder.find(NESTER, pathParts));
+    Assert.assertEquals("c", NestedPathFinder.findStringLiteral(NESTER, 
pathParts));
+
+    pathParts = NestedPathFinder.parseJqPath(".x[-2]");
+    Assert.assertEquals("b", NestedPathFinder.find(NESTER, pathParts));
+    Assert.assertEquals("b", NestedPathFinder.findStringLiteral(NESTER, 
pathParts));
+
+    pathParts = NestedPathFinder.parseJqPath(".x[-4]");
+    Assert.assertNull(NestedPathFinder.find(NESTER, pathParts));
+    Assert.assertNull(NestedPathFinder.findStringLiteral(NESTER, pathParts));
+
     // nonexistent
     pathParts = NestedPathFinder.parseJqPath(".x[1].y.z");
     Assert.assertNull(NestedPathFinder.find(NESTER, pathParts));
diff --git 
a/processing/src/test/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumnTest.java
 
b/processing/src/test/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumnTest.java
index 2e5692b6a1..581c8674da 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumnTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumnTest.java
@@ -91,6 +91,7 @@ public class NestedFieldVirtualColumnTest
   {
     EqualsVerifier.forClass(NestedFieldVirtualColumn.class)
                   .withNonnullFields("columnName", "outputName")
+                  .withIgnoredFields("hasNegativeArrayIndex")
                   .usingGetClass()
                   .verify();
   }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
index b9315033fa..8a05ee74f8 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
@@ -2415,4 +2415,95 @@ public class CalciteNestedDataQueryTest extends 
BaseCalciteQueryTest
                     .build()
     );
   }
+
+  @Test
+  public void testGroupByNegativeJsonPathIndex()
+  {
+    // negative array index cannot vectorize
+    cannotVectorize();
+    testQuery(
+        "SELECT "
+        + "JSON_VALUE(nester, '$.array[-1]'), "
+        + "SUM(cnt) "
+        + "FROM druid.nested GROUP BY 1",
+        ImmutableList.of(
+            GroupByQuery.builder()
+                        .setDataSource(DATA_SOURCE)
+                        .setInterval(querySegmentSpec(Filtration.eternity()))
+                        .setGranularity(Granularities.ALL)
+                        .setVirtualColumns(
+                            new NestedFieldVirtualColumn("nester", 
"$.array[-1]", "v0", ColumnType.STRING)
+                        )
+                        .setDimensions(
+                            dimensions(
+                                new DefaultDimensionSpec("v0", "d0")
+                            )
+                        )
+                        .setAggregatorSpecs(aggregators(new 
LongSumAggregatorFactory("a0", "cnt")))
+                        .setContext(QUERY_CONTEXT_DEFAULT)
+                        .build()
+        ),
+        ImmutableList.of(
+            new Object[]{NullHandling.defaultStringValue(), 5L},
+            new Object[]{"b", 2L}
+        ),
+        RowSignature.builder()
+                    .add("EXPR$0", ColumnType.STRING)
+                    .add("EXPR$1", ColumnType.LONG)
+                    .build()
+    );
+  }
+
+  @Test
+  public void testJsonPathNegativeIndex()
+  {
+    testQuery(
+        "SELECT JSON_VALUE(nester, '$.array[-1]'), JSON_QUERY(nester, 
'$.array[-1]'), JSON_KEYS(nester, '$.array[-1]') FROM druid.nested",
+        ImmutableList.of(
+            Druids.newScanQueryBuilder()
+                  .dataSource(DATA_SOURCE)
+                  .intervals(querySegmentSpec(Filtration.eternity()))
+                  .virtualColumns(
+                      new NestedFieldVirtualColumn(
+                          "nester",
+                          "v0",
+                          ColumnType.STRING,
+                          null,
+                          false,
+                          "$.array[-1]",
+                          false
+                      ),
+                      new NestedFieldVirtualColumn(
+                          "nester",
+                          "v1",
+                          NestedDataComplexTypeSerde.TYPE,
+                          null,
+                          true,
+                          "$.array[-1]",
+                          false
+                      ),
+                      expressionVirtualColumn("v2", 
"json_keys(\"nester\",'$.array[-1]')", ColumnType.STRING_ARRAY)
+                  )
+                  .columns("v0", "v1", "v2")
+                  
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+                  .legacy(false)
+                  .build()
+        ),
+        ImmutableList.of(
+            new Object[]{"b", "\"b\"", null},
+            new Object[]{NullHandling.defaultStringValue(), null, null},
+            new Object[]{NullHandling.defaultStringValue(), null, null},
+            new Object[]{NullHandling.defaultStringValue(), null, null},
+            new Object[]{NullHandling.defaultStringValue(), null, null},
+            new Object[]{"b", "\"b\"", null},
+            new Object[]{NullHandling.defaultStringValue(), null, null}
+        ),
+        RowSignature.builder()
+                    .add("EXPR$0", ColumnType.STRING)
+                    .add("EXPR$1", NestedDataComplexTypeSerde.TYPE)
+                    .add("EXPR$2", ColumnType.STRING_ARRAY)
+                    .build()
+
+    );
+  }
 }


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

Reply via email to