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

cwylie 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 e64b92eb351 add JSON_QUERY_ARRAY function to pluck 
ARRAY<COMPLEX<json>> out of COMPLEX<json> (#15521)
e64b92eb351 is described below

commit e64b92eb35119a0cd404d6a4e93054ce0df2b234
Author: Clint Wylie <[email protected]>
AuthorDate: Fri Dec 8 05:28:46 2023 -0800

    add JSON_QUERY_ARRAY function to pluck ARRAY<COMPLEX<json>> out of 
COMPLEX<json> (#15521)
---
 docs/querying/math-expr.md                         |   1 +
 docs/querying/sql-json-functions.md                |   1 +
 .../org/apache/druid/guice/ExpressionModule.java   |   1 +
 .../query/expression/NestedDataExpressions.java    | 117 +++++++++++
 .../druid/segment/column/ColumnTypeFactory.java    |   2 +-
 .../segment/virtual/NestedFieldVirtualColumn.java  |   7 +
 .../expression/NestedDataExpressionsTest.java      |  34 ++++
 .../builtin/NestedDataOperatorConversions.java     |  32 +++
 .../sql/calcite/planner/DruidOperatorTable.java    |   1 +
 .../sql/calcite/CalciteNestedDataQueryTest.java    | 214 +++++++++++++++++++++
 website/.spelling                                  |   1 +
 11 files changed, 410 insertions(+), 1 deletion(-)

diff --git a/docs/querying/math-expr.md b/docs/querying/math-expr.md
index 2550e30da96..01f9fe95d32 100644
--- a/docs/querying/math-expr.md
+++ b/docs/querying/math-expr.md
@@ -237,6 +237,7 @@ JSON functions provide facilities to extract, transform, 
and create `COMPLEX<jso
 |---|---|
 | json_value(expr, path[, type]) | Extract a Druid literal (`STRING`, `LONG`, 
`DOUBLE`, `ARRAY<STRING>`, `ARRAY<LONG>`, or `ARRAY<DOUBLE>`) value from `expr` 
using JSONPath syntax of `path`. The optional `type` argument can be set to 
`'LONG'`,`'DOUBLE'`, `'STRING'`, `'ARRAY<LONG>'`, `'ARRAY<DOUBLE>'`, or 
`'ARRAY<STRING>'` to cast values to that type. |
 | json_query(expr, path) | Extract a `COMPLEX<json>` value from `expr` using 
JSONPath syntax of `path` |
+| json_query_array(expr, path) | Extract an `ARRAY<COMPLEX<json>>` value from 
`expr` using JSONPath syntax of `path`. If value is not an `ARRAY`, it will be 
translated into a single element `ARRAY` containing the value at `path`. |
 | json_object(expr1, expr2[, expr3, expr4 ...]) | Construct a `COMPLEX<json>` 
with alternating 'key' and 'value' arguments|
 | parse_json(expr) | Deserialize a JSON `STRING` into a `COMPLEX<json>`. If 
the input is not a `STRING` or it is invalid JSON, this function will result in 
an error.|
 | try_parse_json(expr) | Deserialize a JSON `STRING` into a `COMPLEX<json>`. 
If the input is not a `STRING` or it is invalid JSON, this function will result 
in a `NULL` value. |
diff --git a/docs/querying/sql-json-functions.md 
b/docs/querying/sql-json-functions.md
index 1f3c681392e..d499fef5087 100644
--- a/docs/querying/sql-json-functions.md
+++ b/docs/querying/sql-json-functions.md
@@ -40,6 +40,7 @@ You can use the following JSON functions to extract, 
transform, and create `COMP
 |`JSON_OBJECT(KEY expr1 VALUE expr2[, KEY expr3 VALUE expr4, ...])` | 
Constructs a new `COMPLEX<json>` object. The `KEY` expressions must evaluate to 
string types. The `VALUE` expressions can be composed of any input type, 
including other `COMPLEX<json>` values. `JSON_OBJECT` can accept 
colon-separated key-value pairs. The following syntax is equivalent: 
`JSON_OBJECT(expr1:expr2[, expr3:expr4, ...])`.|
 |`JSON_PATHS(expr)`| Returns an array of all paths which refer to literal 
values in `expr` in JSONPath format. |
 |`JSON_QUERY(expr, path)`| Extracts a `COMPLEX<json>` value from `expr`, at 
the specified `path`. |
+|`JSON_QUERY_ARRAY(expr, path)`| Extracts an `ARRAY<COMPLEX<json>>` value from 
`expr`, at the specified `path`. If value is not an `ARRAY`, it will be 
translated into a single element `ARRAY` containing the value at `path`.|
 |`JSON_VALUE(expr, path [RETURNING sqlType])`| Extracts a literal value from 
`expr` at the specified `path`. If you specify `RETURNING` and an SQL type name 
(such as `VARCHAR`, `BIGINT`, `DOUBLE`, etc) the function plans the query using 
the suggested type. Otherwise, it attempts to infer the type based on the 
context. If it can't infer the type, it defaults to `VARCHAR`.|
 |`PARSE_JSON(expr)`|Parses `expr` into a `COMPLEX<json>` object. This operator 
deserializes JSON values when processing them, translating stringified JSON 
into a nested structure. If the input is not a `VARCHAR` or it is invalid JSON, 
this function will result in an error.|
 |`TRY_PARSE_JSON(expr)`|Parses `expr` into a `COMPLEX<json>` object. This 
operator deserializes JSON values when processing them, translating stringified 
JSON into a nested structure. If the input is not a `VARCHAR` or it is invalid 
JSON, this function will result in a `NULL` value.|
diff --git 
a/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java 
b/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
index 7d740721670..917cf967f14 100644
--- a/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
+++ b/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
@@ -85,6 +85,7 @@ public class ExpressionModule implements Module
                    .add(NestedDataExpressions.JsonPathsExprMacro.class)
                    .add(NestedDataExpressions.JsonValueExprMacro.class)
                    .add(NestedDataExpressions.JsonQueryExprMacro.class)
+                   .add(NestedDataExpressions.JsonQueryArrayExprMacro.class)
                    .add(NestedDataExpressions.ToJsonStringExprMacro.class)
                    .add(NestedDataExpressions.ParseJsonExprMacro.class)
                    .add(NestedDataExpressions.TryParseJsonExprMacro.class)
diff --git 
a/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
 
b/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
index fec16ad99b1..f7476adf599 100644
--- 
a/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
+++ 
b/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
@@ -28,6 +28,7 @@ import org.apache.druid.math.expr.ExprEval;
 import org.apache.druid.math.expr.ExprMacroTable;
 import org.apache.druid.math.expr.ExprType;
 import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.ExpressionTypeFactory;
 import org.apache.druid.math.expr.NamedFunction;
 import org.apache.druid.segment.nested.NestedPathFinder;
 import org.apache.druid.segment.nested.NestedPathPart;
@@ -44,6 +45,8 @@ import java.util.stream.Collectors;
 
 public class NestedDataExpressions
 {
+  private static ExpressionType JSON_ARRAY = 
ExpressionTypeFactory.getInstance().ofArray(ExpressionType.NESTED_DATA);
+
   public static class JsonObjectExprMacro implements ExprMacroTable.ExprMacro
   {
     public static final String NAME = "json_object";
@@ -591,6 +594,120 @@ public class NestedDataExpressions
     }
   }
 
+  public static class JsonQueryArrayExprMacro implements 
ExprMacroTable.ExprMacro
+  {
+    public static final String NAME = "json_query_array";
+
+    @Override
+    public String name()
+    {
+      return NAME;
+    }
+
+    @Override
+    public Expr apply(List<Expr> args)
+    {
+      if (args.get(1).isLiteral()) {
+        return new JsonQueryArrayExpr(args);
+      } else {
+        return new JsonQueryArrayDynamicExpr(args);
+      }
+    }
+
+    final class JsonQueryArrayExpr extends 
ExprMacroTable.BaseScalarMacroFunctionExpr
+    {
+      private final List<NestedPathPart> parts;
+
+      public JsonQueryArrayExpr(List<Expr> args)
+      {
+        super(name(), args);
+        this.parts = getJsonPathPartsFromLiteral(JsonQueryArrayExprMacro.this, 
args.get(1));
+      }
+
+      @Override
+      public ExprEval eval(ObjectBinding bindings)
+      {
+        ExprEval input = args.get(0).eval(bindings);
+        final Object value = NestedPathFinder.find(unwrap(input), parts);
+        if (value instanceof List) {
+          return ExprEval.ofArray(
+              JSON_ARRAY,
+              ExprEval.bestEffortArray((List) value).asArray()
+          );
+        }
+        return ExprEval.ofArray(
+            JSON_ARRAY,
+            ExprEval.bestEffortOf(value).asArray()
+        );
+      }
+
+      @Override
+      public Expr visit(Shuttle shuttle)
+      {
+        List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
+        if (newArgs.get(1).isLiteral()) {
+          return shuttle.visit(new JsonQueryArrayExpr(newArgs));
+        } else {
+          return shuttle.visit(new JsonQueryArrayDynamicExpr(newArgs));
+        }
+      }
+
+      @Nullable
+      @Override
+      public ExpressionType getOutputType(InputBindingInspector inspector)
+      {
+        // call all the output JSON typed
+        return ExpressionType.NESTED_DATA;
+      }
+    }
+
+    final class JsonQueryArrayDynamicExpr extends 
ExprMacroTable.BaseScalarMacroFunctionExpr
+    {
+      public JsonQueryArrayDynamicExpr(List<Expr> args)
+      {
+        super(name(), args);
+      }
+
+      @Override
+      public ExprEval eval(ObjectBinding bindings)
+      {
+        ExprEval input = args.get(0).eval(bindings);
+        ExprEval path = args.get(1).eval(bindings);
+        final List<NestedPathPart> parts = 
NestedPathFinder.parseJsonPath(path.asString());
+        final Object value = NestedPathFinder.find(unwrap(input), parts);
+        if (value instanceof List) {
+          return ExprEval.ofArray(
+              JSON_ARRAY,
+              ExprEval.bestEffortArray((List) value).asArray()
+          );
+        }
+        return ExprEval.ofArray(
+            JSON_ARRAY,
+            ExprEval.bestEffortOf(value).asArray()
+        );
+      }
+
+      @Override
+      public Expr visit(Shuttle shuttle)
+      {
+        List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
+        if (newArgs.get(1).isLiteral()) {
+          return shuttle.visit(new JsonQueryArrayExpr(newArgs));
+        } else {
+          return shuttle.visit(new JsonQueryArrayDynamicExpr(newArgs));
+        }
+      }
+
+      @Nullable
+      @Override
+      public ExpressionType getOutputType(InputBindingInspector inspector)
+      {
+        // call all the output ARRAY<COMPLEX<json>> typed
+        return JSON_ARRAY;
+      }
+    }
+  }
+
   public static class JsonPathsExprMacro implements ExprMacroTable.ExprMacro
   {
     public static final String NAME = "json_paths";
diff --git 
a/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
 
b/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
index 49793fba416..c9b153bf121 100644
--- 
a/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
@@ -64,7 +64,7 @@ public class ColumnTypeFactory implements 
TypeFactory<ColumnType>
           case STRING:
             return ColumnType.STRING_ARRAY;
           default:
-            throw new ISE("Unsupported expression type[%s]", 
type.asTypeString());
+            return ColumnType.ofArray(ofType(type.getElementType()));
         }
       case COMPLEX:
         return INTERNER.intern(new ColumnType(ValueType.COMPLEX, 
type.getComplexTypeName(), null));
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 e486e7b7738..0a282f5e4bc 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
@@ -1228,6 +1228,13 @@ public class NestedFieldVirtualColumn implements 
VirtualColumn
   public ColumnCapabilities capabilities(ColumnInspector inspector, String 
columnName)
   {
     if (processFromRaw) {
+      if (expectedType != null && expectedType.isArray() && 
ColumnType.NESTED_DATA.equals(expectedType.getElementType())) {
+        // arrays of objects!
+        return ColumnCapabilitiesImpl.createDefault()
+                                     
.setType(ColumnType.ofArray(ColumnType.NESTED_DATA))
+                                     .setHasMultipleValues(false)
+                                     .setHasNulls(true);
+      }
       // JSON_QUERY always returns a StructuredData
       return ColumnCapabilitiesImpl.createDefault()
                                    .setType(ColumnType.NESTED_DATA)
diff --git 
a/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
 
b/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
index d8bd2d93841..b14edb2d17b 100644
--- 
a/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
@@ -29,6 +29,7 @@ import org.apache.druid.math.expr.ExprEval;
 import org.apache.druid.math.expr.ExprMacroTable;
 import org.apache.druid.math.expr.ExpressionProcessingException;
 import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.ExpressionTypeFactory;
 import org.apache.druid.math.expr.InputBindings;
 import org.apache.druid.math.expr.Parser;
 import org.apache.druid.segment.nested.StructuredData;
@@ -37,6 +38,7 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 
 public class NestedDataExpressionsTest extends InitializedNullHandlingTest
@@ -49,6 +51,7 @@ public class NestedDataExpressionsTest extends 
InitializedNullHandlingTest
           new NestedDataExpressions.JsonObjectExprMacro(),
           new NestedDataExpressions.JsonValueExprMacro(),
           new NestedDataExpressions.JsonQueryExprMacro(),
+          new NestedDataExpressions.JsonQueryArrayExprMacro(),
           new NestedDataExpressions.ToJsonStringExprMacro(JSON_MAPPER),
           new NestedDataExpressions.ParseJsonExprMacro(JSON_MAPPER),
           new NestedDataExpressions.TryParseJsonExprMacro(JSON_MAPPER)
@@ -329,6 +332,37 @@ public class NestedDataExpressionsTest extends 
InitializedNullHandlingTest
     Assert.assertEquals(ExpressionType.NESTED_DATA, eval.type());
   }
 
+  @Test
+  public void testJsonQueryArrayExpression()
+  {
+    final ExpressionType nestedArray = 
ExpressionTypeFactory.getInstance().ofArray(ExpressionType.NESTED_DATA);
+
+    Expr expr = Parser.parse("json_query_array(nest, '$.x')", MACRO_TABLE);
+    ExprEval eval = expr.eval(inputBindings);
+    Assert.assertArrayEquals(new Object[]{100L}, (Object[]) eval.value());
+    Assert.assertEquals(nestedArray, eval.type());
+
+    expr = Parser.parse("json_query_array(nester, '$.x')", MACRO_TABLE);
+    eval = expr.eval(inputBindings);
+    Assert.assertArrayEquals(((List) NESTER.get("x")).toArray(), (Object[]) 
eval.value());
+    Assert.assertEquals(nestedArray, eval.type());
+
+    expr = Parser.parse("json_query_array(nester, 
array_offset(json_paths(nester), 0))", MACRO_TABLE);
+    eval = expr.eval(inputBindings);
+    Assert.assertArrayEquals(((List) NESTER.get("x")).toArray(), (Object[]) 
eval.value());
+    Assert.assertEquals(nestedArray, eval.type());
+
+    expr = Parser.parse("json_query_array(nesterer, '$.y')", MACRO_TABLE);
+    eval = expr.eval(inputBindings);
+    Assert.assertArrayEquals(((List) NESTERER.get("y")).toArray(), (Object[]) 
eval.value());
+    Assert.assertEquals(nestedArray, eval.type());
+
+    expr = Parser.parse("array_length(json_query_array(nesterer, '$.y'))", 
MACRO_TABLE);
+    eval = expr.eval(inputBindings);
+    Assert.assertEquals(3L, eval.value());
+    Assert.assertEquals(ExpressionType.LONG, eval.type());
+  }
+
   @Test
   public void testParseJsonTryParseJson() throws JsonProcessingException
   {
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
index bfa8f47e56e..a51a3d71375 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
@@ -48,11 +48,13 @@ import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.math.expr.Expr;
 import org.apache.druid.math.expr.InputBindings;
+import org.apache.druid.query.expression.NestedDataExpressions;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.segment.nested.NestedPathFinder;
 import org.apache.druid.segment.nested.NestedPathPart;
 import org.apache.druid.segment.virtual.NestedFieldVirtualColumn;
+import org.apache.druid.sql.calcite.expression.DirectOperatorConversion;
 import org.apache.druid.sql.calcite.expression.DruidExpression;
 import org.apache.druid.sql.calcite.expression.Expressions;
 import org.apache.druid.sql.calcite.expression.OperatorConversions;
@@ -78,6 +80,16 @@ public class NestedDataOperatorConversions
       true
   );
 
+  public static final SqlReturnTypeInference 
NESTED_ARRAY_RETURN_TYPE_INFERENCE = opBinding ->
+      opBinding.getTypeFactory().createArrayType(
+          RowSignatures.makeComplexType(
+              opBinding.getTypeFactory(),
+              ColumnType.NESTED_DATA,
+              true
+          ),
+          -1
+      );
+
   public static class JsonPathsOperatorConversion implements 
SqlOperatorConversion
   {
     private static final SqlFunction SQL_FUNCTION = OperatorConversions
@@ -231,6 +243,26 @@ public class NestedDataOperatorConversions
     }
   }
 
+  public static class JsonQueryArrayOperatorConversion extends 
DirectOperatorConversion
+  {
+    private static final SqlFunction SQL_FUNCTION = OperatorConversions
+        
.operatorBuilder(StringUtils.toUpperCase(NestedDataExpressions.JsonQueryArrayExprMacro.NAME))
+        .operandTypeChecker(
+            OperandTypes.family(
+                SqlTypeFamily.ANY,
+                SqlTypeFamily.CHARACTER
+            )
+        )
+        .returnTypeInference(NESTED_ARRAY_RETURN_TYPE_INFERENCE)
+        .functionCategory(SqlFunctionCategory.SYSTEM)
+        .build();
+
+    public JsonQueryArrayOperatorConversion()
+    {
+      super(SQL_FUNCTION, NestedDataExpressions.JsonQueryArrayExprMacro.NAME);
+    }
+  }
+
   /**
    * The {@link org.apache.calcite.sql2rel.StandardConvertletTable} converts 
json_value(.. RETURNING type) into
    * cast(json_value_any(..), type).
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
index ef866aa23cb..a1356fde4c9 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
@@ -339,6 +339,7 @@ public class DruidOperatorTable implements SqlOperatorTable
                    .add(new 
NestedDataOperatorConversions.JsonKeysOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonPathsOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonQueryOperatorConversion())
+                   .add(new 
NestedDataOperatorConversions.JsonQueryArrayOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonValueAnyOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonValueBigintOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonValueDoubleOperatorConversion())
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 cea1bdbef7e..8261694df5b 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
@@ -6523,4 +6523,218 @@ public class CalciteNestedDataQueryTest extends 
BaseCalciteQueryTest
 
     );
   }
+
+  @Test
+  public void testJsonQueryArrays()
+  {
+    cannotVectorize();
+    testBuilder()
+        .sql("SELECT JSON_QUERY_ARRAY(arrayObject, '$') FROM druid.arrays")
+        .queryContext(QUERY_CONTEXT_DEFAULT)
+        .expectedQueries(
+            ImmutableList.of(
+                Druids.newScanQueryBuilder()
+                      .dataSource(DATA_SOURCE_ARRAYS)
+                      .intervals(querySegmentSpec(Filtration.eternity()))
+                      .virtualColumns(
+                          expressionVirtualColumn("v0", 
"json_query_array(\"arrayObject\",'$')", 
ColumnType.ofArray(ColumnType.NESTED_DATA))
+                      )
+                      .columns("v0")
+                      .context(QUERY_CONTEXT_DEFAULT)
+                      .legacy(false)
+                      
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+                      .build()
+            )
+        )
+        .expectedResults(
+            ImmutableList.of(
+                new Object[]{"[{\"x\":1000},{\"y\":2000}]"},
+                new Object[]{"[{\"x\":1},{\"x\":2}]"},
+                new Object[]{"[{\"x\":null},{\"x\":2}]"},
+                new Object[]{"[{\"a\":1},{\"b\":2}]"},
+                new Object[]{"[{\"x\":1},{\"x\":2}]"},
+                new Object[]{"[null,{\"x\":2}]"},
+                new Object[]{"[{\"x\":3},{\"x\":4}]"},
+                new Object[]{"[{\"x\":1000},{\"y\":2000}]"},
+                new Object[]{"[{\"x\":1},{\"x\":2}]"},
+                new Object[]{"[{\"x\":null},{\"x\":2}]"},
+                new Object[]{"[{\"a\":1},{\"b\":2}]"},
+                new Object[]{"[{\"x\":1},{\"x\":2}]"},
+                new Object[]{"[null,{\"x\":2}]"},
+                new Object[]{"[{\"x\":3},{\"x\":4}]"}
+            )
+        )
+        .expectedSignature(
+            RowSignature.builder()
+                        .add("EXPR$0", 
ColumnType.ofArray(ColumnType.NESTED_DATA))
+                        .build()
+        )
+        .run();
+  }
+  @Test
+  public void testUnnestJsonQueryArrays()
+  {
+    cannotVectorize();
+    testBuilder()
+        .sql("SELECT objects FROM druid.arrays, 
UNNEST(JSON_QUERY_ARRAY(arrayObject, '$')) as u(objects)")
+        .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+        .expectedQueries(
+            ImmutableList.of(
+                Druids.newScanQueryBuilder()
+                      .dataSource(
+                          UnnestDataSource.create(
+                              TableDataSource.create(DATA_SOURCE_ARRAYS),
+                              expressionVirtualColumn("j0.unnest", 
"json_query_array(\"arrayObject\",'$')", 
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+                              null
+                          )
+                      )
+                      .intervals(querySegmentSpec(Filtration.eternity()))
+                      .columns("j0.unnest")
+                      .context(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+                      .legacy(false)
+                      
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+                      .build()
+            )
+        )
+        .expectedResults(
+            ImmutableList.of(
+                new Object[]{"{\"x\":1000}"},
+                new Object[]{"{\"y\":2000}"},
+                new Object[]{"{\"x\":1}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"x\":null}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"a\":1}"},
+                new Object[]{"{\"b\":2}"},
+                new Object[]{"{\"x\":1}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{null},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"x\":3}"},
+                new Object[]{"{\"x\":4}"},
+                new Object[]{"{\"x\":1000}"},
+                new Object[]{"{\"y\":2000}"},
+                new Object[]{"{\"x\":1}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"x\":null}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"a\":1}"},
+                new Object[]{"{\"b\":2}"},
+                new Object[]{"{\"x\":1}"},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{null},
+                new Object[]{"{\"x\":2}"},
+                new Object[]{"{\"x\":3}"},
+                new Object[]{"{\"x\":4}"}
+            )
+        )
+        .expectedSignature(
+            RowSignature.builder()
+                        .add("objects", ColumnType.NESTED_DATA)
+                        .build()
+        )
+        .run();
+  }
+
+  @Test
+  public void testUnnestJsonQueryArraysJsonValue()
+  {
+    cannotVectorize();
+    testBuilder()
+        .sql(
+            "SELECT"
+            + " json_value(objects, '$.x' returning bigint) as x,"
+            + " count(*)"
+            + " FROM druid.arrays, UNNEST(JSON_QUERY_ARRAY(arrayObject, '$')) 
as u(objects)"
+            + " GROUP BY 1"
+        )
+        .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+        .expectedQueries(
+            ImmutableList.of(
+                GroupByQuery.builder()
+                            .setDataSource(
+                                UnnestDataSource.create(
+                                    TableDataSource.create(DATA_SOURCE_ARRAYS),
+                                    expressionVirtualColumn("j0.unnest", 
"json_query_array(\"arrayObject\",'$')", 
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+                                    null
+                                )
+                            )
+                            
.setInterval(querySegmentSpec(Filtration.eternity()))
+                            .setGranularity(Granularities.ALL)
+                            .setVirtualColumns(
+                                new NestedFieldVirtualColumn("j0.unnest", 
"$.x", "v0", ColumnType.LONG)
+                            )
+                            .setDimensions(
+                                dimensions(
+                                    new DefaultDimensionSpec("v0", "d0", 
ColumnType.LONG)
+                                )
+                            )
+                            .setAggregatorSpecs(new 
CountAggregatorFactory("a0"))
+                            .setContext(QUERY_CONTEXT_DEFAULT)
+                            .build()
+            )
+        )
+        .expectedResults(
+            ImmutableList.of(
+                new Object[]{NullHandling.defaultLongValue(), 10L},
+                new Object[]{1L, 4L},
+                new Object[]{2L, 8L},
+                new Object[]{3L, 2L},
+                new Object[]{4L, 2L},
+                new Object[]{1000L, 2L}
+            )
+        )
+        .expectedSignature(
+            RowSignature.builder()
+                        .add("x", ColumnType.LONG)
+                        .add("EXPR$1", ColumnType.LONG)
+                        .build()
+        )
+        .run();
+  }
+
+  @Test
+  public void testUnnestJsonQueryArraysJsonValueSum()
+  {
+    cannotVectorize();
+    testBuilder()
+        .sql(
+            "SELECT"
+            + " sum(json_value(objects, '$.x' returning bigint)) as xs"
+            + " FROM druid.arrays, UNNEST(JSON_QUERY_ARRAY(arrayObject, '$')) 
as u(objects)"
+        )
+        .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+        .expectedQueries(
+            ImmutableList.of(
+                Druids.newTimeseriesQueryBuilder()
+                      .intervals(querySegmentSpec(Filtration.eternity()))
+                      .dataSource(
+                          UnnestDataSource.create(
+                              TableDataSource.create(DATA_SOURCE_ARRAYS),
+                              expressionVirtualColumn("j0.unnest", 
"json_query_array(\"arrayObject\",'$')", 
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+                              null
+                          )
+                      )
+                      .virtualColumns(
+                          new NestedFieldVirtualColumn("j0.unnest", "$.x", 
"v0", ColumnType.LONG)
+                      )
+                      .aggregators(
+                          new LongSumAggregatorFactory("a0", "v0")
+                      )
+                      .context(QUERY_CONTEXT_DEFAULT)
+                      .build()
+            )
+        )
+        .expectedResults(
+            ImmutableList.of(
+                new Object[]{2034L}
+            )
+        )
+        .expectedSignature(
+            RowSignature.builder()
+                        .add("xs", ColumnType.LONG)
+                        .build()
+        )
+        .run();
+  }
 }
diff --git a/website/.spelling b/website/.spelling
index e5b8f3cb494..b23c250c8ba 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -361,6 +361,7 @@ json_keys
 json_object
 json_paths
 json_query
+json_query_array
 json_value
 karlkfi
 kerberos


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

Reply via email to